<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <title>魔方游戏</title>
    <meta name="viewport"
        content="width=device-width,height=device-height,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0" />
    <style>
        @font-face {
            font-family: 'BungeeFont';
            font-weight: normal;
            font-style: normal;
            src: url('data:font/truetype;charset=utf-8;base64,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') format('woff2'), url('data:font/truetype;charset=utf-8;base64,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') format('woff');
        }

        *,
        *:before,
        *:after {
            -webkit-user-select: none;
            -moz-user-select: none;
            user-select: none;
            box-sizing: border-box;
            cursor: inherit;
            margin: 0;
            padding: 0;
            outline: none;
            font-size: inherit;
            font-family: inherit;
            font-weight: inherit;
            font-style: inherit;
            text-transform: uppercase;
        }

        *:focus {
            outline: none;
        }

        html {
            -webkit-tap-highlight-color: transparent;
            -webkit-text-size-adjust: 100%;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
            -ms-text-size-adjust: 100%;
            -webkit-text-size-adjust: 100%;
            overflow: hidden;
            height: 100%;
        }

        body {
            font-family: 'BungeeFont', sans-serif;
            font-weight: normal;
            font-style: normal;
            line-height: 1;
            cursor: default;
            overflow: hidden;
            height: 100%;
            font-size: 5rem;
        }

        .icon {
            display: inline-block;
            font-size: inherit;
            overflow: visible;
            vertical-align: -0.125em;
            preserveaspectratio: none;
        }

        .range {
            position: relative;
            width: 14em;
            z-index: 1;
            opacity: 0;
        }

        .range:not(:last-child) {
            margin-bottom: 2em;
        }

        .range__label {
            position: relative;
            font-size: 0.9em;
            line-height: 0.75em;
            padding-bottom: 0.5em;
            z-index: 2;
        }

        .range__track {
            position: relative;
            height: 1em;
            margin-left: 0.5em;
            margin-right: 0.5em;
            z-index: 3;
        }

        .range__track-line {
            position: absolute;
            background: rgba(0, 0, 0, 0.2);
            height: 2px;
            top: 50%;
            margin-top: -1px;
            left: -0.5em;
            right: -0.5em;
            transform-origin: left center;
        }

        .range__handle {
            position: absolute;
            width: 0;
            height: 0;
            top: 50%;
            left: 0;
            cursor: pointer;
            z-index: 1;
        }

        .range__handle div {
            transition: background 500ms ease;
            position: absolute;
            left: 0;
            top: 0;
            width: 0.9em;
            height: 0.9em;
            border-radius: 0.2em;
            margin-left: -0.45em;
            margin-top: -0.45em;
            background: #41aac8;
            border-bottom: 2px solid rgba(0, 0, 0, 0.2);
        }

        .range.is-active .range__handle div {
            transform: scale(1.25);
        }

        .range__handle:after {
            content: '';
            position: absolute;
            left: 0;
            top: 0;
            width: 3em;
            height: 3em;
            margin-left: -1.5em;
            margin-top: -1.5em;
        }

        .range__list {
            display: flex;
            flex-flow: row nowrap;
            justify-content: space-between;
            position: relative;
            padding-top: 0.5em;
            font-size: 0.55em;
            color: rgba(0, 0, 0, 0.5);
            z-index: 1;
        }

        .range--type-color:not(:last-child) {
            margin-bottom: 1em;
        }

        .range--type-color .range__list {
            display: none;
        }

        .range--type-color .range__handle>div {
            background: currentColor !important;
        }

        .range--type-color .range__track-line {
            background: transparent;
        }

        .range--type-color .range__track-line:after {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            content: '';
            opacity: 0.5;
        }

        .range--color-hue .range__handle {
            color: red;
        }

        .range--color-hue .range__track {
            color: red;
        }

        .range--color-hue .range__track-line:after {
            background: linear-gradient(to right,
                    red,
                    yellow,
                    lime,
                    cyan,
                    blue,
                    magenta,
                    red);
        }

        .range--color-saturation .range__handle {
            color: red;
        }

        .range--color-saturation .range__track {
            color: red;
        }

        .range--color-saturation .range__track-line:after {
            background: linear-gradient(to right, gray, currentColor);
        }

        .range--color-lightness .range__handle {
            color: red;
        }

        .range--color-lightness .range__track {
            color: red;
        }

        .range--color-lightness .range__track-line:after {
            background: linear-gradient(to right, black, currentColor, white);
        }

        .stats {
            position: relative;
            width: 14em;
            z-index: 1;
            display: flex;
            justify-content: space-between;
            opacity: 0;
        }

        .stats:not(:last-child) {
            margin-bottom: 1.5em;
        }

        .stats>i {
            display: block;
            color: rgba(0, 0, 0, 0.5);
            font-size: 0.9em;
        }

        .stats>b {
            display: block;
            font-size: 0.9em;
        }

        .stats>b>i {
            font-size: 0.75em;
        }

        .stats[name='worst-time'] {
            display: none;
        }

        .text {
            position: absolute;
            left: 0;
            right: 0;
            text-align: center;
            line-height: 0.75;
            perspective: 100rem;
            opacity: 0;
        }

        .text i {
            display: inline-block;
            opacity: 0;
            white-space: pre-wrap;
        }

        .text--title {
            bottom: 75%;
            font-size: 4.4em;
            height: 1.2em;
        }

        .text--title span {
            display: block;
        }

        .text--title span:first-child {
            font-size: 0.5em;
            margin-bottom: 0.2em;
        }

        .text--note {
            top: 87%;
            font-size: 1em;
        }

        .text--timer {
            bottom: 78%;
            font-size: 3.5em;
            line-height: 1;
        }

        .text--complete,
        .text--best-time {
            font-size: 1.5em;
            top: 83%;
            line-height: 1em;
        }

        .btn {
            -webkit-appearance: none;
            -moz-appearance: none;
            appearance: none;
            background-color: transparent;
            border-radius: 0;
            border-width: 0;
            position: absolute;
            pointer-events: none;
            font-size: 1.2em;
            color: rgba(0, 0, 0, 0.25);
            opacity: 0;
        }

        .btn:after {
            position: absolute;
            content: '';
            width: 3em;
            height: 3em;
            left: 50%;
            top: 50%;
            margin-left: -1.5em;
            margin-top: -1.5em;
            border-radius: 100%;
        }

        .btn--bl {
            bottom: 0.8em;
            left: 0.8em;
        }

        .btn--br {
            bottom: 0.8em;
            right: 0.8em;
        }

        .btn--bc {
            bottom: 0.8em;
            left: calc(50% - 0.5em);
        }

        .btn svg {
            display: block;
        }

        .btn--cancel {
            display: none !important;
        }

        .ui {
            pointer-events: none;
            color: #070d15;
        }

        .ui,
        .ui__background,
        .ui__game,
        .ui__texts,
        .ui__prefs,
        .ui__theme,
        .ui__stats,
        .ui__buttons {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            overflow: hidden;
        }

        .ui__background {
            z-index: 1;
            transition: background 500ms ease;
            background: #d1d5db;
        }

        .ui__background:after {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            content: '';
            background-image: linear-gradient(to bottom,
                    white 50%,
                    rgba(255, 255, 255, 0) 100%);
        }

        .ui__game {
            pointer-events: all;
            z-index: 2;
        }

        .ui__game canvas {
            display: block;
            width: 100%;
            height: 100%;
        }

        .ui__texts {
            z-index: 3;
        }

        .ui__prefs,
        .ui__stats,
        .ui__theme {
            display: flex;
            flex-flow: column nowrap;
            justify-content: center;
            align-items: center;
            overflow: hidden;
            z-index: 4;
        }

        .ui__theme {
            padding-top: 15em;
        }

        .ui__buttons {
            z-index: 5;
        }
    </style>
</head>

<body>
    <div class="ui">
        <div class="ui__background"></div>

        <div class="ui__game"></div>

        <div class="ui__texts">
            <h1 class="text text--title">
                <span>RUBIKS</span>
                <span>CUBE</span>
            </h1>
            <div class="text text--note">Double tap to start</div>
            <div class="text text--timer">0:00</div>
            <div class="text text--complete">
                <span>Complete!</span>
            </div>
            <div class="text text--best-time">
                <icon trophy></icon>
                <span>Best Time!</span>
            </div>
        </div>

        <div class="ui__prefs">
            <range name="size" title="Cube Size" list="2,3,4,5"></range>
            <range name="flip" title="Flip Type" list="Swift&nbsp;,Smooth,Bounce"></range>
            <range name="scramble" title="Scramble Length" list="20,25,30"></range>
            <range name="fov" title="Camera Angle" list="Ortographic,Perspective"></range>
            <range name="theme" title="Color Scheme" list="Cube,Erno,Dust,Camo,Rain"></range>
        </div>

        <div class="ui__theme">
            <range name="hue" title="Hue" color></range>
            <range name="saturation" title="Saturation" color></range>
            <range name="lightness" title="Lightness" color></range>
        </div>

        <div class="ui__stats">
            <div class="stats" name="cube-size"><i>Cube:</i><b>3x3x3</b></div>
            <div class="stats" name="total-solves">
                <i>Total solves:</i><b>-</b>
            </div>
            <div class="stats" name="best-time"><i>Best time:</i><b>-</b></div>
            <div class="stats" name="worst-time"><i>Worst time:</i><b>-</b></div>
            <div class="stats" name="average-5"><i>Average of 5:</i><b>-</b></div>
            <div class="stats" name="average-12"><i>Average of 12:</i><b>-</b></div>
            <div class="stats" name="average-25"><i>Average of 25:</i><b>-</b></div>
        </div>

        <div class="ui__buttons">
            <button class="btn btn--bl btn--stats">
                <icon trophy></icon>
            </button>
            <button class="btn btn--br btn--prefs">
                <icon settings></icon>
            </button>
            <button class="btn btn--bl btn--back">
                <icon back></icon>
            </button>
            <button class="btn btn--br btn--theme">
                <icon theme></icon>
            </button>
            <button class="btn btn--br btn--reset">
                <icon reset></icon>
            </button>
        </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/95/three.min.js"></script>
    <script>
        const animationEngine = (() => {
            let uniqueID = 0;

            class AnimationEngine {
                constructor() {
                    this.ids = [];
                    this.animations = {};
                    this.update = this.update.bind(this);
                    this.raf = 0;
                    this.time = 0;
                }

                update() {
                    const now = performance.now();
                    const delta = now - this.time;
                    this.time = now;

                    let i = this.ids.length;

                    this.raf = i ? requestAnimationFrame(this.update) : 0;

                    while (i--)
                        this.animations[this.ids[i]] &&
                            this.animations[this.ids[i]].update(delta);
                }

                add(animation) {
                    animation.id = uniqueID++;

                    this.ids.push(animation.id);
                    this.animations[animation.id] = animation;

                    if (this.raf !== 0) return;

                    this.time = performance.now();
                    this.raf = requestAnimationFrame(this.update);
                }

                remove(animation) {
                    const index = this.ids.indexOf(animation.id);

                    if (index < 0) return;

                    this.ids.splice(index, 1);
                    delete this.animations[animation.id];
                    animation = null;
                }
            }

            return new AnimationEngine();
        })();

        class Animation {
            constructor(start) {
                if (start === true) this.start();
            }

            start() {
                animationEngine.add(this);
            }

            stop() {
                animationEngine.remove(this);
            }

            update(delta) { }
        }

        class World extends Animation {
            constructor(game) {
                super(true);

                this.game = game;

                this.container = this.game.dom.game;
                this.scene = new THREE.Scene();

                this.renderer = new THREE.WebGLRenderer({
                    antialias: true,
                    alpha: true,
                });
                this.renderer.setPixelRatio(window.devicePixelRatio);
                this.container.appendChild(this.renderer.domElement);

                this.camera = new THREE.PerspectiveCamera(2, 1, 0.1, 10000);

                this.stage = { width: 2, height: 3 };
                this.fov = 10;

                this.createLights();

                this.onResize = [];

                this.resize();
                window.addEventListener('resize', () => this.resize(), false);
            }

            update() {
                this.renderer.render(this.scene, this.camera);
            }

            resize() {
                this.width = this.container.offsetWidth;
                this.height = this.container.offsetHeight;

                this.renderer.setSize(this.width, this.height);

                this.camera.fov = this.fov;
                this.camera.aspect = this.width / this.height;

                const aspect = this.stage.width / this.stage.height;
                const fovRad = this.fov * THREE.Math.DEG2RAD;

                let distance =
                    aspect < this.camera.aspect
                        ? this.stage.height / 2 / Math.tan(fovRad / 2)
                        : this.stage.width /
                        this.camera.aspect /
                        (2 * Math.tan(fovRad / 2));

                distance *= 0.5;

                this.camera.position.set(distance, distance, distance);
                this.camera.lookAt(this.scene.position);
                this.camera.updateProjectionMatrix();

                const docFontSize =
                    aspect < this.camera.aspect
                        ? (this.height / 100) * aspect
                        : this.width / 100;

                document.documentElement.style.fontSize = docFontSize + 'px';

                if (this.onResize) this.onResize.forEach((cb) => cb());
            }

            createLights() {
                this.lights = {
                    holder: new THREE.Object3D(),
                    ambient: new THREE.AmbientLight(0xffffff, 0.69),
                    front: new THREE.DirectionalLight(0xffffff, 0.36),
                    back: new THREE.DirectionalLight(0xffffff, 0.19),
                };

                this.lights.front.position.set(1.5, 5, 3);
                this.lights.back.position.set(-1.5, -5, -3);

                this.lights.holder.add(this.lights.ambient);
                this.lights.holder.add(this.lights.front);
                this.lights.holder.add(this.lights.back);

                this.scene.add(this.lights.holder);
            }
        }

        function RoundedBoxGeometry(size, radius, radiusSegments) {
            THREE.BufferGeometry.call(this);

            this.type = 'RoundedBoxGeometry';

            radiusSegments = !isNaN(radiusSegments)
                ? Math.max(1, Math.floor(radiusSegments))
                : 1;

            var width, height, depth;

            width = height = depth = size;
            radius = size * radius;

            radius = Math.min(
                radius,
                Math.min(width, Math.min(height, Math.min(depth))) / 2
            );

            var edgeHalfWidth = width / 2 - radius;
            var edgeHalfHeight = height / 2 - radius;
            var edgeHalfDepth = depth / 2 - radius;

            this.parameters = {
                width: width,
                height: height,
                depth: depth,
                radius: radius,
                radiusSegments: radiusSegments,
            };

            var rs1 = radiusSegments + 1;
            var totalVertexCount = (rs1 * radiusSegments + 1) << 3;

            var positions = new THREE.BufferAttribute(
                new Float32Array(totalVertexCount * 3),
                3
            );
            var normals = new THREE.BufferAttribute(
                new Float32Array(totalVertexCount * 3),
                3
            );

            var cornerVerts = [],
                cornerNormals = [],
                normal = new THREE.Vector3(),
                vertex = new THREE.Vector3(),
                vertexPool = [],
                normalPool = [],
                indices = [];
            var lastVertex = rs1 * radiusSegments,
                cornerVertNumber = rs1 * radiusSegments + 1;
            doVertices();
            doFaces();
            doCorners();
            doHeightEdges();
            doWidthEdges();
            doDepthEdges();

            function doVertices() {
                var cornerLayout = [
                    new THREE.Vector3(1, 1, 1),
                    new THREE.Vector3(1, 1, -1),
                    new THREE.Vector3(-1, 1, -1),
                    new THREE.Vector3(-1, 1, 1),
                    new THREE.Vector3(1, -1, 1),
                    new THREE.Vector3(1, -1, -1),
                    new THREE.Vector3(-1, -1, -1),
                    new THREE.Vector3(-1, -1, 1),
                ];

                for (var j = 0; j < 8; j++) {
                    cornerVerts.push([]);
                    cornerNormals.push([]);
                }

                var PIhalf = Math.PI / 2;
                var cornerOffset = new THREE.Vector3(
                    edgeHalfWidth,
                    edgeHalfHeight,
                    edgeHalfDepth
                );

                for (var y = 0; y <= radiusSegments; y++) {
                    var v = y / radiusSegments;
                    var va = v * PIhalf;
                    var cosVa = Math.cos(va);
                    var sinVa = Math.sin(va);

                    if (y == radiusSegments) {
                        vertex.set(0, 1, 0);
                        var vert = vertex
                            .clone()
                            .multiplyScalar(radius)
                            .add(cornerOffset);
                        cornerVerts[0].push(vert);
                        vertexPool.push(vert);
                        var norm = vertex.clone();
                        cornerNormals[0].push(norm);
                        normalPool.push(norm);
                        continue;
                    }

                    for (var x = 0; x <= radiusSegments; x++) {
                        var u = x / radiusSegments;
                        var ha = u * PIhalf;
                        vertex.x = cosVa * Math.cos(ha);
                        vertex.y = sinVa;
                        vertex.z = cosVa * Math.sin(ha);

                        var vert = vertex
                            .clone()
                            .multiplyScalar(radius)
                            .add(cornerOffset);
                        cornerVerts[0].push(vert);
                        vertexPool.push(vert);

                        var norm = vertex.clone().normalize();
                        cornerNormals[0].push(norm);
                        normalPool.push(norm);
                    }
                }

                for (var i = 1; i < 8; i++) {
                    for (var j = 0; j < cornerVerts[0].length; j++) {
                        var vert = cornerVerts[0][j].clone().multiply(cornerLayout[i]);
                        cornerVerts[i].push(vert);
                        vertexPool.push(vert);

                        var norm = cornerNormals[0][j].clone().multiply(cornerLayout[i]);
                        cornerNormals[i].push(norm);
                        normalPool.push(norm);
                    }
                }
            }

            function doCorners() {
                var flips = [true, false, true, false, false, true, false, true];

                var lastRowOffset = rs1 * (radiusSegments - 1);

                for (var i = 0; i < 8; i++) {
                    var cornerOffset = cornerVertNumber * i;

                    for (var v = 0; v < radiusSegments - 1; v++) {
                        var r1 = v * rs1;
                        var r2 = (v + 1) * rs1;

                        for (var u = 0; u < radiusSegments; u++) {
                            var u1 = u + 1;
                            var a = cornerOffset + r1 + u;
                            var b = cornerOffset + r1 + u1;
                            var c = cornerOffset + r2 + u;
                            var d = cornerOffset + r2 + u1;

                            if (!flips[i]) {
                                indices.push(a);
                                indices.push(b);
                                indices.push(c);

                                indices.push(b);
                                indices.push(d);
                                indices.push(c);
                            } else {
                                indices.push(a);
                                indices.push(c);
                                indices.push(b);

                                indices.push(b);
                                indices.push(c);
                                indices.push(d);
                            }
                        }
                    }

                    for (var u = 0; u < radiusSegments; u++) {
                        var a = cornerOffset + lastRowOffset + u;
                        var b = cornerOffset + lastRowOffset + u + 1;
                        var c = cornerOffset + lastVertex;

                        if (!flips[i]) {
                            indices.push(a);
                            indices.push(b);
                            indices.push(c);
                        } else {
                            indices.push(a);
                            indices.push(c);
                            indices.push(b);
                        }
                    }
                }
            }

            function doFaces() {
                var a = lastVertex;
                var b = lastVertex + cornerVertNumber;
                var c = lastVertex + cornerVertNumber * 2;
                var d = lastVertex + cornerVertNumber * 3;

                indices.push(a);
                indices.push(b);
                indices.push(c);
                indices.push(a);
                indices.push(c);
                indices.push(d);

                a = lastVertex + cornerVertNumber * 4;
                b = lastVertex + cornerVertNumber * 5;
                c = lastVertex + cornerVertNumber * 6;
                d = lastVertex + cornerVertNumber * 7;

                indices.push(a);
                indices.push(c);
                indices.push(b);
                indices.push(a);
                indices.push(d);
                indices.push(c);

                a = 0;
                b = cornerVertNumber;
                c = cornerVertNumber * 4;
                d = cornerVertNumber * 5;

                indices.push(a);
                indices.push(c);
                indices.push(b);
                indices.push(b);
                indices.push(c);
                indices.push(d);

                a = cornerVertNumber * 2;
                b = cornerVertNumber * 3;
                c = cornerVertNumber * 6;
                d = cornerVertNumber * 7;

                indices.push(a);
                indices.push(c);
                indices.push(b);
                indices.push(b);
                indices.push(c);
                indices.push(d);

                a = radiusSegments;
                b = radiusSegments + cornerVertNumber * 3;
                c = radiusSegments + cornerVertNumber * 4;
                d = radiusSegments + cornerVertNumber * 7;

                indices.push(a);
                indices.push(b);
                indices.push(c);
                indices.push(b);
                indices.push(d);
                indices.push(c);

                a = radiusSegments + cornerVertNumber;
                b = radiusSegments + cornerVertNumber * 2;
                c = radiusSegments + cornerVertNumber * 5;
                d = radiusSegments + cornerVertNumber * 6;

                indices.push(a);
                indices.push(c);
                indices.push(b);
                indices.push(b);
                indices.push(c);
                indices.push(d);
            }

            function doHeightEdges() {
                for (var i = 0; i < 4; i++) {
                    var cOffset = i * cornerVertNumber;
                    var cRowOffset = 4 * cornerVertNumber + cOffset;
                    var needsFlip = i & (1 === 1);

                    for (var u = 0; u < radiusSegments; u++) {
                        var u1 = u + 1;
                        var a = cOffset + u;
                        var b = cOffset + u1;
                        var c = cRowOffset + u;
                        var d = cRowOffset + u1;

                        if (!needsFlip) {
                            indices.push(a);
                            indices.push(b);
                            indices.push(c);
                            indices.push(b);
                            indices.push(d);
                            indices.push(c);
                        } else {
                            indices.push(a);
                            indices.push(c);
                            indices.push(b);
                            indices.push(b);
                            indices.push(c);
                            indices.push(d);
                        }
                    }
                }
            }

            function doDepthEdges() {
                var cStarts = [0, 2, 4, 6];
                var cEnds = [1, 3, 5, 7];

                for (var i = 0; i < 4; i++) {
                    var cStart = cornerVertNumber * cStarts[i];
                    var cEnd = cornerVertNumber * cEnds[i];

                    var needsFlip = 1 >= i;

                    for (var u = 0; u < radiusSegments; u++) {
                        var urs1 = u * rs1;
                        var u1rs1 = (u + 1) * rs1;

                        var a = cStart + urs1;
                        var b = cStart + u1rs1;
                        var c = cEnd + urs1;
                        var d = cEnd + u1rs1;

                        if (needsFlip) {
                            indices.push(a);
                            indices.push(c);
                            indices.push(b);
                            indices.push(b);
                            indices.push(c);
                            indices.push(d);
                        } else {
                            indices.push(a);
                            indices.push(b);
                            indices.push(c);
                            indices.push(b);
                            indices.push(d);
                            indices.push(c);
                        }
                    }
                }
            }

            function doWidthEdges() {
                var end = radiusSegments - 1;

                var cStarts = [0, 1, 4, 5];
                var cEnds = [3, 2, 7, 6];
                var needsFlip = [0, 1, 1, 0];

                for (var i = 0; i < 4; i++) {
                    var cStart = cStarts[i] * cornerVertNumber;
                    var cEnd = cEnds[i] * cornerVertNumber;

                    for (var u = 0; u <= end; u++) {
                        var a = cStart + radiusSegments + u * rs1;
                        var b =
                            cStart +
                            (u != end
                                ? radiusSegments + (u + 1) * rs1
                                : cornerVertNumber - 1);

                        var c = cEnd + radiusSegments + u * rs1;
                        var d =
                            cEnd +
                            (u != end
                                ? radiusSegments + (u + 1) * rs1
                                : cornerVertNumber - 1);

                        if (!needsFlip[i]) {
                            indices.push(a);
                            indices.push(b);
                            indices.push(c);
                            indices.push(b);
                            indices.push(d);
                            indices.push(c);
                        } else {
                            indices.push(a);
                            indices.push(c);
                            indices.push(b);
                            indices.push(b);
                            indices.push(c);
                            indices.push(d);
                        }
                    }
                }
            }

            var index = 0;

            for (var i = 0; i < vertexPool.length; i++) {
                positions.setXYZ(
                    index,
                    vertexPool[i].x,
                    vertexPool[i].y,
                    vertexPool[i].z
                );

                normals.setXYZ(
                    index,
                    normalPool[i].x,
                    normalPool[i].y,
                    normalPool[i].z
                );

                index++;
            }

            this.setIndex(new THREE.BufferAttribute(new Uint16Array(indices), 1));
            this.addAttribute('position', positions);
            this.addAttribute('normal', normals);
        }

        RoundedBoxGeometry.prototype = Object.create(
            THREE.BufferGeometry.prototype
        );
        RoundedBoxGeometry.constructor = RoundedBoxGeometry;

        function RoundedPlaneGeometry(size, radius, depth) {
            var x, y, width, height;

            x = y = -size / 2;
            width = height = size;
            radius = size * radius;

            const shape = new THREE.Shape();

            shape.moveTo(x, y + radius);
            shape.lineTo(x, y + height - radius);
            shape.quadraticCurveTo(x, y + height, x + radius, y + height);
            shape.lineTo(x + width - radius, y + height);
            shape.quadraticCurveTo(
                x + width,
                y + height,
                x + width,
                y + height - radius
            );
            shape.lineTo(x + width, y + radius);
            shape.quadraticCurveTo(x + width, y, x + width - radius, y);
            shape.lineTo(x + radius, y);
            shape.quadraticCurveTo(x, y, x, y + radius);

            const geometry = new THREE.ExtrudeBufferGeometry(shape, {
                depth: depth,
                bevelEnabled: false,
                curveSegments: 3,
            });

            return geometry;
        }

        class Cube {
            constructor(game) {
                this.game = game;
                this.size = 3;

                this.geometry = {
                    pieceCornerRadius: 0.12,
                    edgeCornerRoundness: 0.15,
                    edgeScale: 0.82,
                    edgeDepth: 0.01,
                };

                this.holder = new THREE.Object3D();
                this.object = new THREE.Object3D();
                this.animator = new THREE.Object3D();

                this.holder.add(this.animator);
                this.animator.add(this.object);

                this.game.world.scene.add(this.holder);
            }

            init() {
                this.cubes = [];
                this.object.children = [];
                this.object.add(this.game.controls.group);

                if (this.size === 2) this.scale = 1.25;
                else if (this.size === 3) this.scale = 1;
                else if (this.size > 3) this.scale = 3 / this.size;

                this.object.scale.set(this.scale, this.scale, this.scale);

                const controlsScale = this.size === 2 ? 0.825 : 1;
                this.game.controls.edges.scale.set(
                    controlsScale,
                    controlsScale,
                    controlsScale
                );

                this.generatePositions();
                this.generateModel();

                this.pieces.forEach((piece) => {
                    this.cubes.push(piece.userData.cube);
                    this.object.add(piece);
                });

                this.holder.traverse((node) => {
                    if (node.frustumCulled) node.frustumCulled = false;
                });

                this.updateColors(this.game.themes.getColors());

                this.sizeGenerated = this.size;
            }

            resize(force = false) {
                if (this.size !== this.sizeGenerated || force) {
                    this.size = this.game.preferences.ranges.size.value;

                    this.reset();
                    this.init();

                    this.game.saved = false;
                    this.game.timer.reset();
                    this.game.storage.clearGame();
                }
            }

            reset() {
                this.game.controls.edges.rotation.set(0, 0, 0);

                this.holder.rotation.set(0, 0, 0);
                this.object.rotation.set(0, 0, 0);
                this.animator.rotation.set(0, 0, 0);
            }

            generatePositions() {
                const m = this.size - 1;
                const first =
                    this.size % 2 !== 0
                        ? 0 - Math.floor(this.size / 2)
                        : 0.5 - this.size / 2;

                let x, y, z;

                this.positions = [];

                for (x = 0; x < this.size; x++) {
                    for (y = 0; y < this.size; y++) {
                        for (z = 0; z < this.size; z++) {
                            let position = new THREE.Vector3(
                                first + x,
                                first + y,
                                first + z
                            );
                            let edges = [];

                            if (x == 0) edges.push(0);
                            if (x == m) edges.push(1);
                            if (y == 0) edges.push(2);
                            if (y == m) edges.push(3);
                            if (z == 0) edges.push(4);
                            if (z == m) edges.push(5);

                            position.edges = edges;
                            this.positions.push(position);
                        }
                    }
                }
            }

            generateModel() {
                this.pieces = [];
                this.edges = [];

                const pieceSize = 1 / 3;

                const mainMaterial = new THREE.MeshLambertMaterial();

                const pieceMesh = new THREE.Mesh(
                    new RoundedBoxGeometry(
                        pieceSize,
                        this.geometry.pieceCornerRadius,
                        3
                    ),
                    mainMaterial.clone()
                );

                const edgeGeometry = RoundedPlaneGeometry(
                    pieceSize,
                    this.geometry.edgeCornerRoundness,
                    this.geometry.edgeDepth
                );

                this.positions.forEach((position, index) => {
                    const piece = new THREE.Object3D();
                    const pieceCube = pieceMesh.clone();
                    const pieceEdges = [];

                    piece.position.copy(position.clone().divideScalar(3));
                    piece.add(pieceCube);
                    piece.name = index;
                    piece.edgesName = '';

                    position.edges.forEach((position) => {
                        const edge = new THREE.Mesh(edgeGeometry, mainMaterial.clone());
                        const name = ['L', 'R', 'D', 'U', 'B', 'F'][position];
                        const distance = pieceSize / 2;

                        edge.position.set(
                            distance * [-1, 1, 0, 0, 0, 0][position],
                            distance * [0, 0, -1, 1, 0, 0][position],
                            distance * [0, 0, 0, 0, -1, 1][position]
                        );

                        edge.rotation.set(
                            (Math.PI / 2) * [0, 0, 1, -1, 0, 0][position],
                            (Math.PI / 2) * [-1, 1, 0, 0, 2, 0][position],
                            0
                        );

                        edge.scale.set(
                            this.geometry.edgeScale,
                            this.geometry.edgeScale,
                            this.geometry.edgeScale
                        );

                        edge.name = name;

                        piece.add(edge);
                        pieceEdges.push(name);
                        this.edges.push(edge);
                    });

                    piece.userData.edges = pieceEdges;
                    piece.userData.cube = pieceCube;

                    piece.userData.start = {
                        position: piece.position.clone(),
                        rotation: piece.rotation.clone(),
                    };

                    this.pieces.push(piece);
                });
            }

            updateColors(colors) {
                if (typeof this.pieces !== 'object' && typeof this.edges !== 'object')
                    return;

                this.pieces.forEach((piece) =>
                    piece.userData.cube.material.color.setHex(colors.P)
                );
                this.edges.forEach((edge) =>
                    edge.material.color.setHex(colors[edge.name])
                );
            }

            loadFromData(data) {
                this.size = data.size;

                this.reset();
                this.init();

                this.pieces.forEach((piece) => {
                    const index = data.names.indexOf(piece.name);

                    const position = data.positions[index];
                    const rotation = data.rotations[index];

                    piece.position.set(position.x, position.y, position.z);
                    piece.rotation.set(rotation.x, rotation.y, rotation.z);
                });
            }
        }

        const Easing = {
            Power: {
                In: (power) => {
                    power = Math.round(power || 1);

                    return (t) => Math.pow(t, power);
                },

                Out: (power) => {
                    power = Math.round(power || 1);

                    return (t) => 1 - Math.abs(Math.pow(t - 1, power));
                },

                InOut: (power) => {
                    power = Math.round(power || 1);

                    return (t) =>
                        t < 0.5
                            ? Math.pow(t * 2, power) / 2
                            : (1 - Math.abs(Math.pow(t * 2 - 1 - 1, power))) / 2 + 0.5;
                },
            },

            Sine: {
                In: () => (t) => 1 + Math.sin((Math.PI / 2) * t - Math.PI / 2),

                Out: () => (t) => Math.sin((Math.PI / 2) * t),

                InOut: () => (t) => (1 + Math.sin(Math.PI * t - Math.PI / 2)) / 2,
            },

            Back: {
                Out: (s) => {
                    s = s || 1.70158;

                    return (t) => {
                        return (t -= 1) * t * ((s + 1) * t + s) + 1;
                    };
                },

                In: (s) => {
                    s = s || 1.70158;

                    return (t) => {
                        return t * t * ((s + 1) * t - s);
                    };
                },
            },

            Elastic: {
                Out: (amplitude, period) => {
                    let PI2 = Math.PI * 2;

                    let p1 = amplitude >= 1 ? amplitude : 1;
                    let p2 = (period || 0.3) / (amplitude < 1 ? amplitude : 1);
                    let p3 = (p2 / PI2) * (Math.asin(1 / p1) || 0);

                    p2 = PI2 / p2;

                    return (t) => {
                        return p1 * Math.pow(2, -10 * t) * Math.sin((t - p3) * p2) + 1;
                    };
                },
            },
        };

        class Tween extends Animation {
            constructor(options) {
                super(false);

                this.duration = options.duration || 500;
                this.easing = options.easing || ((t) => t);
                this.onUpdate = options.onUpdate || (() => { });
                this.onComplete = options.onComplete || (() => { });

                this.delay = options.delay || false;
                this.yoyo = options.yoyo ? false : null;

                this.progress = 0;
                this.value = 0;
                this.delta = 0;

                this.getFromTo(options);

                if (this.delay) setTimeout(() => super.start(), this.delay);
                else super.start();

                this.onUpdate(this);
            }

            update(delta) {
                const old = this.value * 1;
                const direction = this.yoyo === true ? -1 : 1;

                this.progress += (delta / this.duration) * direction;

                this.value = this.easing(this.progress);
                this.delta = this.value - old;

                if (this.values !== null) this.updateFromTo();

                if (this.yoyo !== null) this.updateYoyo();
                else if (this.progress <= 1) this.onUpdate(this);
                else {
                    this.progress = 1;
                    this.value = 1;
                    this.onUpdate(this);
                    this.onComplete(this);
                    super.stop();
                }
            }

            updateYoyo() {
                if (this.progress > 1 || this.progress < 0) {
                    this.value = this.progress = this.progress > 1 ? 1 : 0;
                    this.yoyo = !this.yoyo;
                }

                this.onUpdate(this);
            }

            updateFromTo() {
                this.values.forEach((key) => {
                    this.target[key] =
                        this.from[key] + (this.to[key] - this.from[key]) * this.value;
                });
            }

            getFromTo(options) {
                if (!options.target || !options.to) {
                    this.values = null;
                    return;
                }

                this.target = options.target || null;
                this.from = options.from || {};
                this.to = options.to || null;
                this.values = [];

                if (Object.keys(this.from).length < 1)
                    Object.keys(this.to).forEach((key) => {
                        this.from[key] = this.target[key];
                    });

                Object.keys(this.to).forEach((key) => {
                    this.values.push(key);
                });
            }
        }

        window.addEventListener('touchmove', () => { });
        document.addEventListener(
            'touchmove',
            (event) => {
                event.preventDefault();
            },
            { passive: false }
        );

        class Draggable {
            constructor(element, options) {
                this.position = {
                    current: new THREE.Vector2(),
                    start: new THREE.Vector2(),
                    delta: new THREE.Vector2(),
                    old: new THREE.Vector2(),
                    drag: new THREE.Vector2(),
                };

                this.options = Object.assign(
                    {
                        calcDelta: false,
                    },
                    options || {}
                );

                this.element = element;
                this.touch = null;

                this.drag = {
                    start: (event) => {
                        if (event.type == 'mousedown' && event.which != 1) return;
                        if (event.type == 'touchstart' && event.touches.length > 1)
                            return;

                        this.getPositionCurrent(event);

                        if (this.options.calcDelta) {
                            this.position.start = this.position.current.clone();
                            this.position.delta.set(0, 0);
                            this.position.drag.set(0, 0);
                        }

                        this.touch = event.type == 'touchstart';

                        this.onDragStart(this.position);

                        window.addEventListener(
                            this.touch ? 'touchmove' : 'mousemove',
                            this.drag.move,
                            false
                        );
                        window.addEventListener(
                            this.touch ? 'touchend' : 'mouseup',
                            this.drag.end,
                            false
                        );
                    },

                    move: (event) => {
                        if (this.options.calcDelta) {
                            this.position.old = this.position.current.clone();
                        }

                        this.getPositionCurrent(event);

                        if (this.options.calcDelta) {
                            this.position.delta = this.position.current
                                .clone()
                                .sub(this.position.old);
                            this.position.drag = this.position.current
                                .clone()
                                .sub(this.position.start);
                        }

                        this.onDragMove(this.position);
                    },

                    end: (event) => {
                        this.getPositionCurrent(event);

                        this.onDragEnd(this.position);

                        window.removeEventListener(
                            this.touch ? 'touchmove' : 'mousemove',
                            this.drag.move,
                            false
                        );
                        window.removeEventListener(
                            this.touch ? 'touchend' : 'mouseup',
                            this.drag.end,
                            false
                        );
                    },
                };

                this.onDragStart = () => { };
                this.onDragMove = () => { };
                this.onDragEnd = () => { };

                this.enable();

                return this;
            }

            enable() {
                this.element.addEventListener('touchstart', this.drag.start, false);
                this.element.addEventListener('mousedown', this.drag.start, false);

                return this;
            }

            disable() {
                this.element.removeEventListener(
                    'touchstart',
                    this.drag.start,
                    false
                );
                this.element.removeEventListener('mousedown', this.drag.start, false);

                return this;
            }

            getPositionCurrent(event) {
                const dragEvent = event.touches
                    ? event.touches[0] || event.changedTouches[0]
                    : event;

                this.position.current.set(dragEvent.pageX, dragEvent.pageY);
            }

            convertPosition(position) {
                position.x = (position.x / this.element.offsetWidth) * 2 - 1;
                position.y = -((position.y / this.element.offsetHeight) * 2 - 1);

                return position;
            }
        }

        const STILL = 0;
        const PREPARING = 1;
        const ROTATING = 2;
        const ANIMATING = 3;

        class Controls {
            constructor(game) {
                this.game = game;

                this.flipConfig = 0;

                this.flipEasings = [
                    Easing.Power.Out(3),
                    Easing.Sine.Out(),
                    Easing.Back.Out(1.5),
                ];
                this.flipSpeeds = [125, 200, 300];

                this.raycaster = new THREE.Raycaster();

                const helperMaterial = new THREE.MeshBasicMaterial({
                    depthWrite: false,
                    transparent: true,
                    opacity: 0,
                    color: 0x0033ff,
                });

                this.group = new THREE.Object3D();
                this.group.name = 'controls';
                this.game.cube.object.add(this.group);

                this.helper = new THREE.Mesh(
                    new THREE.PlaneBufferGeometry(200, 200),
                    helperMaterial.clone()
                );

                this.helper.rotation.set(0, Math.PI / 4, 0);
                this.game.world.scene.add(this.helper);

                this.edges = new THREE.Mesh(
                    new THREE.BoxBufferGeometry(1, 1, 1),
                    helperMaterial.clone()
                );

                this.game.world.scene.add(this.edges);

                this.onSolved = () => { };
                this.onMove = () => { };

                this.momentum = [];

                this.scramble = null;
                this.state = STILL;
                this.enabled = false;

                this.initDraggable();
            }

            enable() {
                this.draggable.enable();
                this.enabled = true;
            }

            disable() {
                this.draggable.disable();
                this.enabled = false;
            }

            initDraggable() {
                this.draggable = new Draggable(this.game.dom.game);

                this.draggable.onDragStart = (position) => {
                    if (this.scramble !== null) return;
                    if (this.state === PREPARING || this.state === ROTATING) return;

                    this.gettingDrag = this.state === ANIMATING;

                    const edgeIntersect = this.getIntersect(
                        position.current,
                        this.edges,
                        false
                    );

                    if (edgeIntersect !== false) {
                        this.dragIntersect = this.getIntersect(
                            position.current,
                            this.game.cube.cubes,
                            true
                        );
                    }

                    if (edgeIntersect !== false && this.dragIntersect !== false) {
                        this.dragNormal = edgeIntersect.face.normal.round();
                        this.flipType = 'layer';

                        this.attach(this.helper, this.edges);

                        this.helper.rotation.set(0, 0, 0);
                        this.helper.position.set(0, 0, 0);
                        this.helper.lookAt(this.dragNormal);
                        this.helper.translateZ(0.5);
                        this.helper.updateMatrixWorld();

                        this.detach(this.helper, this.edges);
                    } else {
                        this.dragNormal = new THREE.Vector3(0, 0, 1);
                        this.flipType = 'cube';

                        this.helper.position.set(0, 0, 0);
                        this.helper.rotation.set(0, Math.PI / 4, 0);
                        this.helper.updateMatrixWorld();
                    }

                    let planeIntersect = this.getIntersect(
                        position.current,
                        this.helper,
                        false
                    );
                    if (planeIntersect === false) return;

                    this.dragCurrent = this.helper.worldToLocal(planeIntersect.point);
                    this.dragTotal = new THREE.Vector3();
                    this.state = this.state === STILL ? PREPARING : this.state;
                };

                this.draggable.onDragMove = (position) => {
                    if (this.scramble !== null) return;
                    if (
                        this.state === STILL ||
                        (this.state === ANIMATING && this.gettingDrag === false)
                    )
                        return;

                    const planeIntersect = this.getIntersect(
                        position.current,
                        this.helper,
                        false
                    );
                    if (planeIntersect === false) return;

                    const point = this.helper.worldToLocal(
                        planeIntersect.point.clone()
                    );

                    this.dragDelta = point.clone().sub(this.dragCurrent).setZ(0);
                    this.dragTotal.add(this.dragDelta);
                    this.dragCurrent = point;
                    this.addMomentumPoint(this.dragDelta);

                    if (this.state === PREPARING && this.dragTotal.length() > 0.05) {
                        this.dragDirection = this.getMainAxis(this.dragTotal);

                        if (this.flipType === 'layer') {
                            const direction = new THREE.Vector3();
                            direction[this.dragDirection] = 1;

                            const worldDirection = this.helper
                                .localToWorld(direction)
                                .sub(this.helper.position);
                            const objectDirection = this.edges
                                .worldToLocal(worldDirection)
                                .round();

                            this.flipAxis = objectDirection.cross(this.dragNormal).negate();

                            this.selectLayer(this.getLayer(false));
                        } else {
                            const axis =
                                this.dragDirection != 'x'
                                    ? this.dragDirection == 'y' &&
                                        position.current.x > this.game.world.width / 2
                                        ? 'z'
                                        : 'x'
                                    : 'y';

                            this.flipAxis = new THREE.Vector3();
                            this.flipAxis[axis] = 1 * (axis == 'x' ? -1 : 1);
                        }

                        this.flipAngle = 0;
                        this.state = ROTATING;
                    } else if (this.state === ROTATING) {
                        const rotation = this.dragDelta[this.dragDirection];

                        if (this.flipType === 'layer') {
                            this.group.rotateOnAxis(this.flipAxis, rotation);
                            this.flipAngle += rotation;
                        } else {
                            this.edges.rotateOnWorldAxis(this.flipAxis, rotation);
                            this.game.cube.object.rotation.copy(this.edges.rotation);
                            this.flipAngle += rotation;
                        }
                    }
                };

                this.draggable.onDragEnd = (position) => {
                    if (this.scramble !== null) return;
                    if (this.state !== ROTATING) {
                        this.gettingDrag = false;
                        this.state = STILL;
                        return;
                    }

                    this.state = ANIMATING;

                    const momentum = this.getMomentum()[this.dragDirection];
                    const flip =
                        Math.abs(momentum) > 0.05 &&
                        Math.abs(this.flipAngle) < Math.PI / 2;

                    const angle = flip
                        ? this.roundAngle(
                            this.flipAngle + Math.sign(this.flipAngle) * (Math.PI / 4)
                        )
                        : this.roundAngle(this.flipAngle);

                    const delta = angle - this.flipAngle;

                    if (this.flipType === 'layer') {
                        this.rotateLayer(delta, false, (layer) => {
                            this.game.storage.saveGame();

                            this.state = this.gettingDrag ? PREPARING : STILL;
                            this.gettingDrag = false;

                            this.checkIsSolved();
                        });
                    } else {
                        this.rotateCube(delta, () => {
                            this.state = this.gettingDrag ? PREPARING : STILL;
                            this.gettingDrag = false;
                        });
                    }
                };
            }

            rotateLayer(rotation, scramble, callback) {
                const config = scramble ? 0 : this.flipConfig;

                const easing = this.flipEasings[config];
                const duration = this.flipSpeeds[config];
                const bounce = config == 2 ? this.bounceCube() : () => { };

                this.rotationTween = new Tween({
                    easing: easing,
                    duration: duration,
                    onUpdate: (tween) => {
                        let deltaAngle = tween.delta * rotation;
                        this.group.rotateOnAxis(this.flipAxis, deltaAngle);
                        bounce(tween.value, deltaAngle, rotation);
                    },
                    onComplete: () => {
                        if (!scramble) this.onMove();

                        const layer = this.flipLayer.slice(0);

                        this.game.cube.object.rotation.setFromVector3(
                            this.snapRotation(this.game.cube.object.rotation.toVector3())
                        );
                        this.group.rotation.setFromVector3(
                            this.snapRotation(this.group.rotation.toVector3())
                        );
                        this.deselectLayer(this.flipLayer);

                        callback(layer);
                    },
                });
            }

            bounceCube() {
                let fixDelta = true;

                return (progress, delta, rotation) => {
                    if (progress >= 1) {
                        if (fixDelta) {
                            delta = (progress - 1) * rotation;
                            fixDelta = false;
                        }

                        this.game.cube.object.rotateOnAxis(this.flipAxis, delta);
                    }
                };
            }

            rotateCube(rotation, callback) {
                const config = this.flipConfig;
                const easing = [
                    Easing.Power.Out(4),
                    Easing.Sine.Out(),
                    Easing.Back.Out(2),
                ][config];
                const duration = [100, 150, 350][config];

                this.rotationTween = new Tween({
                    easing: easing,
                    duration: duration,
                    onUpdate: (tween) => {
                        this.edges.rotateOnWorldAxis(
                            this.flipAxis,
                            tween.delta * rotation
                        );
                        this.game.cube.object.rotation.copy(this.edges.rotation);
                    },
                    onComplete: () => {
                        this.edges.rotation.setFromVector3(
                            this.snapRotation(this.edges.rotation.toVector3())
                        );
                        this.game.cube.object.rotation.copy(this.edges.rotation);
                        callback();
                    },
                });
            }

            selectLayer(layer) {
                this.group.rotation.set(0, 0, 0);
                this.movePieces(layer, this.game.cube.object, this.group);
                this.flipLayer = layer;
            }

            deselectLayer(layer) {
                this.movePieces(layer, this.group, this.game.cube.object);
                this.flipLayer = null;
            }

            movePieces(layer, from, to) {
                from.updateMatrixWorld();
                to.updateMatrixWorld();

                layer.forEach((index) => {
                    const piece = this.game.cube.pieces[index];

                    piece.applyMatrix(from.matrixWorld);
                    from.remove(piece);
                    piece.applyMatrix(new THREE.Matrix4().getInverse(to.matrixWorld));
                    to.add(piece);
                });
            }

            getLayer(position) {
                const scalar = { 2: 6, 3: 3, 4: 4, 5: 3 }[this.game.cube.size];
                const layer = [];

                let axis;

                if (position === false) {
                    const piece = this.dragIntersect.object.parent;

                    axis = this.getMainAxis(this.flipAxis);
                    position = piece.position.clone().multiplyScalar(scalar).round();
                } else {
                    axis = this.getMainAxis(position);
                }

                this.game.cube.pieces.forEach((piece) => {
                    const piecePosition = piece.position
                        .clone()
                        .multiplyScalar(scalar)
                        .round();

                    if (piecePosition[axis] == position[axis]) layer.push(piece.name);
                });

                return layer;
            }

            keyboardMove(type, move, callback) {
                if (this.state !== STILL) return;
                if (this.enabled !== true) return;

                if (type === 'LAYER') {
                    const layer = this.getLayer(move.position);

                    this.flipAxis = new THREE.Vector3();
                    this.flipAxis[move.axis] = 1;
                    this.state = ROTATING;

                    this.selectLayer(layer);
                    this.rotateLayer(move.angle, false, (layer) => {
                        this.game.storage.saveGame();
                        this.state = STILL;
                        this.checkIsSolved();
                    });
                } else if (type === 'CUBE') {
                    this.flipAxis = new THREE.Vector3();
                    this.flipAxis[move.axis] = 1;
                    this.state = ROTATING;

                    this.rotateCube(move.angle, () => {
                        this.state = STILL;
                    });
                }
            }

            scrambleCube() {
                if (this.scramble == null) {
                    this.scramble = this.game.scrambler;
                    this.scramble.callback =
                        typeof callback !== 'function' ? () => { } : callback;
                }

                const converted = this.scramble.converted;
                const move = converted[0];
                const layer = this.getLayer(move.position);

                this.flipAxis = new THREE.Vector3();
                this.flipAxis[move.axis] = 1;

                this.selectLayer(layer);
                this.rotateLayer(move.angle, true, () => {
                    converted.shift();

                    if (converted.length > 0) {
                        this.scrambleCube();
                    } else {
                        this.scramble = null;
                        this.game.storage.saveGame();
                    }
                });
            }

            getIntersect(position, object, multiple) {
                this.raycaster.setFromCamera(
                    this.draggable.convertPosition(position.clone()),
                    this.game.world.camera
                );

                const intersect = multiple
                    ? this.raycaster.intersectObjects(object)
                    : this.raycaster.intersectObject(object);

                return intersect.length > 0 ? intersect[0] : false;
            }

            getMainAxis(vector) {
                return Object.keys(vector).reduce((a, b) =>
                    Math.abs(vector[a]) > Math.abs(vector[b]) ? a : b
                );
            }

            detach(child, parent) {
                child.applyMatrix(parent.matrixWorld);
                parent.remove(child);
                this.game.world.scene.add(child);
            }

            attach(child, parent) {
                child.applyMatrix(new THREE.Matrix4().getInverse(parent.matrixWorld));
                this.game.world.scene.remove(child);
                parent.add(child);
            }

            addMomentumPoint(delta) {
                const time = Date.now();

                this.momentum = this.momentum.filter(
                    (moment) => time - moment.time < 500
                );

                if (delta !== false) this.momentum.push({ delta, time });
            }

            getMomentum() {
                const points = this.momentum.length;
                const momentum = new THREE.Vector2();

                this.addMomentumPoint(false);

                this.momentum.forEach((point, index) => {
                    momentum.add(point.delta.multiplyScalar(index / points));
                });

                return momentum;
            }

            roundAngle(angle) {
                const round = Math.PI / 2;
                return Math.sign(angle) * Math.round(Math.abs(angle) / round) * round;
            }

            snapRotation(angle) {
                return angle.set(
                    this.roundAngle(angle.x),
                    this.roundAngle(angle.y),
                    this.roundAngle(angle.z)
                );
            }

            checkIsSolved() {
                const start = performance.now();

                let solved = true;
                const sides = {
                    'x-': [],
                    'x+': [],
                    'y-': [],
                    'y+': [],
                    'z-': [],
                    'z+': [],
                };

                this.game.cube.edges.forEach((edge) => {
                    const position = edge.parent
                        .localToWorld(edge.position.clone())
                        .sub(this.game.cube.object.position);

                    const mainAxis = this.getMainAxis(position);
                    const mainSign =
                        position.multiplyScalar(2).round()[mainAxis] < 1 ? '-' : '+';

                    sides[mainAxis + mainSign].push(edge.name);
                });

                Object.keys(sides).forEach((side) => {
                    if (!sides[side].every((value) => value === sides[side][0]))
                        solved = false;
                });

                if (solved) this.onSolved();
            }
        }

        class Scrambler {
            constructor(game) {
                this.game = game;

                this.dificulty = 0;

                this.scrambleLength = {
                    2: [7, 9, 11],
                    3: [20, 25, 30],
                    4: [30, 40, 50],
                    5: [40, 60, 80],
                };

                this.moves = [];
                this.conveted = [];
                this.pring = '';
            }

            scramble(scramble) {
                let count = 0;
                this.moves =
                    typeof scramble !== 'undefined' ? scramble.split(' ') : [];

                if (this.moves.length < 1) {
                    const scrambleLength =
                        this.scrambleLength[this.game.cube.size][this.dificulty];

                    const faces = this.game.cube.size < 4 ? 'UDLRFB' : 'UuDdLlRrFfBb';
                    const modifiers = ['', "'", '2'];
                    const total =
                        typeof scramble === 'undefined' ? scrambleLength : scramble;

                    while (count < total) {
                        const move =
                            faces[Math.floor(Math.random() * faces.length)] +
                            modifiers[Math.floor(Math.random() * 3)];

                        if (
                            count > 0 &&
                            move.charAt(0) == this.moves[count - 1].charAt(0)
                        )
                            continue;
                        if (
                            count > 1 &&
                            move.charAt(0) == this.moves[count - 2].charAt(0)
                        )
                            continue;

                        this.moves.push(move);
                        count++;
                    }
                }

                this.callback = () => { };
                this.convert();
                this.print = this.moves.join(' ');

                return this;
            }

            convert(moves) {
                this.converted = [];

                this.moves.forEach((move) => {
                    const convertedMove = this.convertMove(move);
                    const modifier = move.charAt(1);

                    this.converted.push(convertedMove);
                    if (modifier == '2') this.converted.push(convertedMove);
                });
            }

            convertMove(move) {
                const face = move.charAt(0);
                const modifier = move.charAt(1);

                const axis = { D: 'y', U: 'y', L: 'x', R: 'x', F: 'z', B: 'z' }[
                    face.toUpperCase()
                ];
                let row = { D: -1, U: 1, L: -1, R: 1, F: 1, B: -1 }[
                    face.toUpperCase()
                ];

                if (this.game.cube.size > 3 && face !== face.toLowerCase())
                    row = row * 2;

                const position = new THREE.Vector3();
                position[
                    { D: 'y', U: 'y', L: 'x', R: 'x', F: 'z', B: 'z' }[
                    face.toUpperCase()
                    ]
                ] = row;

                const angle = (Math.PI / 2) * -row * (modifier == "'" ? -1 : 1);

                return { position, axis, angle, name: move };
            }
        }

        class Transition {
            constructor(game) {
                this.game = game;

                this.tweens = {};
                this.durations = {};
                this.data = {
                    cubeY: -0.2,
                    cameraZoom: 0.85,
                };

                this.activeTransitions = 0;
            }

            init() {
                this.game.controls.disable();

                this.game.cube.object.position.y = this.data.cubeY;
                this.game.cube.animator.position.y = 4;
                this.game.cube.animator.rotation.x = -Math.PI / 3;
                this.game.world.camera.zoom = this.data.cameraZoom;
                this.game.world.camera.updateProjectionMatrix();

                this.tweens.buttons = {};
                this.tweens.timer = [];
                this.tweens.title = [];
                this.tweens.best = [];
                this.tweens.complete = [];
                this.tweens.prefs = [];
                this.tweens.theme = [];
                this.tweens.stats = [];
            }

            buttons(show, hide) {
                const buttonTween = (button, show) => {
                    return new Tween({
                        target: button.style,
                        duration: 300,
                        easing: show ? Easing.Power.Out(2) : Easing.Power.In(3),
                        from: { opacity: show ? 0 : 1 },
                        to: { opacity: show ? 1 : 0 },
                        onUpdate: (tween) => {
                            const translate = show ? 1 - tween.value : tween.value;
                            button.style.transform = `translate3d(0, ${translate * 1.5
                                }em, 0)`;
                        },
                        onComplete: () =>
                            (button.style.pointerEvents = show ? 'all' : 'none'),
                    });
                };

                hide.forEach(
                    (button) =>
                    (this.tweens.buttons[button] = buttonTween(
                        this.game.dom.buttons[button],
                        false
                    ))
                );

                setTimeout(
                    () =>
                        show.forEach((button) => {
                            this.tweens.buttons[button] = buttonTween(
                                this.game.dom.buttons[button],
                                true
                            );
                        }),
                    hide ? 500 : 0
                );
            }

            cube(show, theming = false) {
                this.activeTransitions++;

                try {
                    this.tweens.cube.stop();
                } catch (e) { }
                const currentY = this.game.cube.animator.position.y;
                const currentRotation = this.game.cube.animator.rotation.x;

                this.tweens.cube = new Tween({
                    duration: show ? 3000 : 1250,
                    easing: show ? Easing.Elastic.Out(0.8, 0.6) : Easing.Back.In(1),
                    onUpdate: (tween) => {
                        this.game.cube.animator.position.y = show
                            ? theming
                                ? 0.9 + (1 - tween.value) * 3.5
                                : (1 - tween.value) * 4
                            : currentY + tween.value * 4;

                        this.game.cube.animator.rotation.x = show
                            ? ((1 - tween.value) * Math.PI) / 3
                            : currentRotation + (tween.value * -Math.PI) / 3;
                    },
                });

                if (theming) {
                    if (show) {
                        this.game.world.camera.zoom = 0.75;
                        this.game.world.camera.updateProjectionMatrix();
                    } else {
                        setTimeout(() => {
                            this.game.world.camera.zoom = this.data.cameraZoom;
                            this.game.world.camera.updateProjectionMatrix();
                        }, 1500);
                    }
                }

                this.durations.cube = show ? 1500 : 1500;

                setTimeout(() => this.activeTransitions--, this.durations.cube);
            }

            float() {
                try {
                    this.tweens.float.stop();
                } catch (e) { }
                this.tweens.float = new Tween({
                    duration: 1500,
                    easing: Easing.Sine.InOut(),
                    yoyo: true,
                    onUpdate: (tween) => {
                        this.game.cube.holder.position.y = -0.02 + tween.value * 0.04;
                        this.game.cube.holder.rotation.x = 0.005 - tween.value * 0.01;
                        this.game.cube.holder.rotation.z =
                            -this.game.cube.holder.rotation.x;
                        this.game.cube.holder.rotation.y =
                            this.game.cube.holder.rotation.x;

                        this.game.controls.edges.position.y =
                            this.game.cube.holder.position.y +
                            this.game.cube.object.position.y;
                    },
                });
            }

            zoom(play, time) {
                this.activeTransitions++;

                const zoom = play ? 1 : this.data.cameraZoom;
                const duration = time > 0 ? Math.max(time, 1500) : 1500;
                const rotations = time > 0 ? Math.round(duration / 1500) : 1;
                const easing = Easing.Power.InOut(time > 0 ? 2 : 3);

                this.tweens.zoom = new Tween({
                    target: this.game.world.camera,
                    duration: duration,
                    easing: easing,
                    to: { zoom: zoom },
                    onUpdate: () => {
                        this.game.world.camera.updateProjectionMatrix();
                    },
                });

                this.tweens.rotate = new Tween({
                    target: this.game.cube.animator.rotation,
                    duration: duration,
                    easing: easing,
                    to: { y: -Math.PI * 2 * rotations },
                    onComplete: () => {
                        this.game.cube.animator.rotation.y = 0;
                    },
                });

                this.durations.zoom = duration;

                setTimeout(() => this.activeTransitions--, this.durations.zoom);
            }

            elevate(complete) {
                this.activeTransitions++;

                const cubeY = (this.tweens.elevate = new Tween({
                    target: this.game.cube.object.position,
                    duration: complete ? 1500 : 0,
                    easing: Easing.Power.InOut(3),
                    to: { y: complete ? -0.05 : this.data.cubeY },
                }));

                this.durations.elevate = 1500;

                setTimeout(() => this.activeTransitions--, this.durations.elevate);
            }

            complete(show, best) {
                this.activeTransitions++;

                const text = best
                    ? this.game.dom.texts.best
                    : this.game.dom.texts.complete;

                if (text.querySelector('span i') === null)
                    text
                        .querySelectorAll('span')
                        .forEach((span) => this.splitLetters(span));

                const letters = text.querySelectorAll('.icon, i');

                this.flipLetters(best ? 'best' : 'complete', letters, show);

                text.style.opacity = 1;

                const duration = this.durations[best ? 'best' : 'complete'];

                if (!show)
                    setTimeout(
                        () => (this.game.dom.texts.timer.style.transform = ''),
                        duration
                    );

                setTimeout(() => this.activeTransitions--, duration);
            }

            stats(show) {
                if (show) this.game.scores.calcStats();

                this.activeTransitions++;

                this.tweens.stats.forEach((tween) => {
                    tween.stop();
                    tween = null;
                });

                let tweenId = -1;

                const stats = this.game.dom.stats.querySelectorAll('.stats');
                const easing = show ? Easing.Power.Out(2) : Easing.Power.In(3);

                stats.forEach((stat, index) => {
                    const delay = index * (show ? 80 : 60);

                    this.tweens.stats[tweenId++] = new Tween({
                        delay: delay,
                        duration: 400,
                        easing: easing,
                        onUpdate: (tween) => {
                            const translate = show ? (1 - tween.value) * 2 : tween.value;
                            const opacity = show ? tween.value : 1 - tween.value;

                            stat.style.transform = `translate3d(0, ${translate}em, 0)`;
                            stat.style.opacity = opacity;
                        },
                    });
                });

                this.durations.stats = 0;

                setTimeout(() => this.activeTransitions--, this.durations.stats);
            }

            preferences(show) {
                this.ranges(
                    this.game.dom.prefs.querySelectorAll('.range'),
                    'prefs',
                    show
                );
            }

            theming(show) {
                this.ranges(
                    this.game.dom.theme.querySelectorAll('.range'),
                    'prefs',
                    show
                );
            }

            ranges(ranges, type, show) {
                this.activeTransitions++;

                this.tweens[type].forEach((tween) => {
                    tween.stop();
                    tween = null;
                });

                const easing = show ? Easing.Power.Out(2) : Easing.Power.In(3);

                let tweenId = -1;
                let listMax = 0;

                ranges.forEach((range, rangeIndex) => {
                    const label = range.querySelector('.range__label');
                    const track = range.querySelector('.range__track-line');
                    const handle = range.querySelector('.range__handle');
                    const list = range.querySelectorAll('.range__list div');

                    const delay = rangeIndex * (show ? 120 : 100);

                    label.style.opacity = show ? 0 : 1;
                    track.style.opacity = show ? 0 : 1;
                    handle.style.opacity = show ? 0 : 1;
                    handle.style.pointerEvents = show ? 'all' : 'none';

                    this.tweens[type][tweenId++] = new Tween({
                        delay: show ? delay : delay,
                        duration: 400,
                        easing: easing,
                        onUpdate: (tween) => {
                            const translate = show ? 1 - tween.value : tween.value;
                            const opacity = show ? tween.value : 1 - tween.value;

                            label.style.transform = `translate3d(0, ${translate}em, 0)`;
                            label.style.opacity = opacity;
                        },
                    });

                    this.tweens[type][tweenId++] = new Tween({
                        delay: show ? delay + 100 : delay,
                        duration: 400,
                        easing: easing,
                        onUpdate: (tween) => {
                            const translate = show ? 1 - tween.value : tween.value;
                            const scale = show ? tween.value : 1 - tween.value;
                            const opacity = scale;

                            track.style.transform = `translate3d(0, ${translate}em, 0) scale3d(${scale}, 1, 1)`;
                            track.style.opacity = opacity;
                        },
                    });

                    this.tweens[type][tweenId++] = new Tween({
                        delay: show ? delay + 100 : delay,
                        duration: 400,
                        easing: easing,
                        onUpdate: (tween) => {
                            const translate = show ? 1 - tween.value : tween.value;
                            const opacity = 1 - translate;
                            const scale = 0.5 + opacity * 0.5;

                            handle.style.transform = `translate3d(0, ${translate}em, 0) scale3d(${scale}, ${scale}, ${scale})`;
                            handle.style.opacity = opacity;
                        },
                    });

                    list.forEach((listItem, labelIndex) => {
                        listItem.style.opacity = show ? 0 : 1;

                        this.tweens[type][tweenId++] = new Tween({
                            delay: show ? delay + 200 + labelIndex * 50 : delay,
                            duration: 400,
                            easing: easing,
                            onUpdate: (tween) => {
                                const translate = show ? 1 - tween.value : tween.value;
                                const opacity = show ? tween.value : 1 - tween.value;

                                listItem.style.transform = `translate3d(0, ${translate}em, 0)`;
                                listItem.style.opacity = opacity;
                            },
                        });
                    });

                    listMax = list.length > listMax ? list.length - 1 : listMax;

                    range.style.opacity = 1;
                });

                this.durations[type] = show
                    ? (ranges.length - 1) * 100 + 200 + listMax * 50 + 400
                    : (ranges.length - 1) * 100 + 400;

                setTimeout(() => this.activeTransitions--, this.durations[type]);
            }

            title(show) {
                this.activeTransitions++;

                const title = this.game.dom.texts.title;

                if (title.querySelector('span i') === null)
                    title
                        .querySelectorAll('span')
                        .forEach((span) => this.splitLetters(span));

                const letters = title.querySelectorAll('i');

                this.flipLetters('title', letters, show);

                title.style.opacity = 1;

                const note = this.game.dom.texts.note;

                this.tweens.title[letters.length] = new Tween({
                    target: note.style,
                    easing: Easing.Sine.InOut(),
                    duration: show ? 800 : 400,
                    yoyo: show ? true : null,
                    from: {
                        opacity: show ? 0 : parseFloat(getComputedStyle(note).opacity),
                    },
                    to: { opacity: show ? 1 : 0 },
                });

                setTimeout(() => this.activeTransitions--, this.durations.title);
            }

            timer(show) {
                this.activeTransitions++;

                const timer = this.game.dom.texts.timer;

                timer.style.opacity = 0;
                this.game.timer.convert();
                this.game.timer.setText();

                this.splitLetters(timer);
                const letters = timer.querySelectorAll('i');
                this.flipLetters('timer', letters, show);

                timer.style.opacity = 1;

                setTimeout(() => this.activeTransitions--, this.durations.timer);
            }

            splitLetters(element) {
                const text = element.innerHTML;

                element.innerHTML = '';

                text.split('').forEach((letter) => {
                    const i = document.createElement('i');

                    i.innerHTML = letter;

                    element.appendChild(i);
                });
            }

            flipLetters(type, letters, show) {
                try {
                    this.tweens[type].forEach((tween) => tween.stop());
                } catch (e) { }
                letters.forEach((letter, index) => {
                    letter.style.opacity = show ? 0 : 1;

                    this.tweens[type][index] = new Tween({
                        easing: Easing.Sine.Out(),
                        duration: show ? 800 : 400,
                        delay: index * 50,
                        onUpdate: (tween) => {
                            const rotation = show
                                ? (1 - tween.value) * -80
                                : tween.value * 80;

                            letter.style.transform = `rotate3d(0, 1, 0, ${rotation}deg)`;
                            letter.style.opacity = show ? tween.value : 1 - tween.value;
                        },
                    });
                });

                this.durations[type] = (letters.length - 1) * 50 + (show ? 800 : 400);
            }
        }

        class Timer extends Animation {
            constructor(game) {
                super(false);

                this.game = game;
                this.reset();
            }

            start(continueGame) {
                this.startTime = continueGame
                    ? Date.now() - this.deltaTime
                    : Date.now();
                this.deltaTime = 0;
                this.converted = this.convert();

                super.start();
            }

            reset() {
                this.startTime = 0;
                this.currentTime = 0;
                this.deltaTime = 0;
                this.converted = '0:00';
            }

            stop() {
                this.currentTime = Date.now();
                this.deltaTime = this.currentTime - this.startTime;
                this.convert();

                super.stop();

                return { time: this.converted, millis: this.deltaTime };
            }

            update() {
                const old = this.converted;

                this.currentTime = Date.now();
                this.deltaTime = this.currentTime - this.startTime;
                this.convert();

                if (this.converted != old) {
                    localStorage.setItem('theCube_time', this.deltaTime);
                    this.setText();
                }
            }

            convert() {
                const seconds = parseInt((this.deltaTime / 1000) % 60);
                const minutes = parseInt(this.deltaTime / (1000 * 60));

                this.converted = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
            }

            setText() {
                this.game.dom.texts.timer.innerHTML = this.converted;
            }
        }

        const RangeHTML = [
            '<div class="range">',
            '<div class="range__label"></div>',
            '<div class="range__track">',
            '<div class="range__track-line"></div>',
            '<div class="range__handle"><div></div></div>',
            '</div>',
            '<div class="range__list"></div>',
            '</div>',
        ].join('\n');

        document.querySelectorAll('range').forEach((el) => {
            const temp = document.createElement('div');
            temp.innerHTML = RangeHTML;

            const range = temp.querySelector('.range');
            const rangeLabel = range.querySelector('.range__label');
            const rangeList = range.querySelector('.range__list');

            range.setAttribute('name', el.getAttribute('name'));
            rangeLabel.innerHTML = el.getAttribute('title');

            if (el.hasAttribute('color')) {
                range.classList.add('range--type-color');
                range.classList.add('range--color-' + el.getAttribute('name'));
            }

            if (el.hasAttribute('list')) {
                el.getAttribute('list')
                    .split(',')
                    .forEach((listItemText) => {
                        const listItem = document.createElement('div');
                        listItem.innerHTML = listItemText;
                        rangeList.appendChild(listItem);
                    });
            }

            el.parentNode.replaceChild(range, el);
        });

        class Range {
            constructor(name, options) {
                options = Object.assign(
                    {
                        range: [0, 1],
                        value: 0,
                        step: 0,
                        onUpdate: () => { },
                        onComplete: () => { },
                    },
                    options || {}
                );

                this.element = document.querySelector('.range[name="' + name + '"]');
                this.track = this.element.querySelector('.range__track');
                this.handle = this.element.querySelector('.range__handle');
                this.list = [].slice.call(
                    this.element.querySelectorAll('.range__list div')
                );

                this.value = options.value;
                this.min = options.range[0];
                this.max = options.range[1];
                this.step = options.step;

                this.onUpdate = options.onUpdate;
                this.onComplete = options.onComplete;

                this.setValue(this.value);

                this.initDraggable();
            }

            setValue(value) {
                this.value = this.round(this.limitValue(value));
                this.setHandlePosition();
            }

            initDraggable() {
                let current;

                this.draggable = new Draggable(this.handle, { calcDelta: true });

                this.draggable.onDragStart = (position) => {
                    current = this.positionFromValue(this.value);
                    this.handle.style.left = current + 'px';
                };

                this.draggable.onDragMove = (position) => {
                    current = this.limitPosition(current + position.delta.x);
                    this.value = this.round(this.valueFromPosition(current));
                    this.setHandlePosition();

                    this.onUpdate(this.value);
                };

                this.draggable.onDragEnd = (position) => {
                    this.onComplete(this.value);
                };
            }

            round(value) {
                if (this.step < 1) return value;

                return (
                    Math.round((value - this.min) / this.step) * this.step + this.min
                );
            }

            limitValue(value) {
                const max = Math.max(this.max, this.min);
                const min = Math.min(this.max, this.min);

                return Math.min(Math.max(value, min), max);
            }

            limitPosition(position) {
                return Math.min(Math.max(position, 0), this.track.offsetWidth);
            }

            percentsFromValue(value) {
                return (value - this.min) / (this.max - this.min);
            }

            valueFromPosition(position) {
                return (
                    this.min +
                    (this.max - this.min) * (position / this.track.offsetWidth)
                );
            }

            positionFromValue(value) {
                return this.percentsFromValue(value) * this.track.offsetWidth;
            }

            setHandlePosition() {
                this.handle.style.left =
                    this.percentsFromValue(this.value) * 100 + '%';
            }
        }

        class Preferences {
            constructor(game) {
                this.game = game;
            }

            init() {
                this.ranges = {
                    size: new Range('size', {
                        value: this.game.cube.size,
                        range: [2, 5],
                        step: 1,
                        onUpdate: (value) => {
                            this.game.cube.size = value;

                            this.game.preferences.ranges.scramble.list.forEach(
                                (item, i) => {
                                    item.innerHTML =
                                        this.game.scrambler.scrambleLength[this.game.cube.size][
                                        i
                                        ];
                                }
                            );
                        },
                        onComplete: () => this.game.storage.savePreferences(),
                    }),

                    flip: new Range('flip', {
                        value: this.game.controls.flipConfig,
                        range: [0, 2],
                        step: 1,
                        onUpdate: (value) => {
                            this.game.controls.flipConfig = value;
                        },
                        onComplete: () => this.game.storage.savePreferences(),
                    }),

                    scramble: new Range('scramble', {
                        value: this.game.scrambler.dificulty,
                        range: [0, 2],
                        step: 1,
                        onUpdate: (value) => {
                            this.game.scrambler.dificulty = value;
                        },
                        onComplete: () => this.game.storage.savePreferences(),
                    }),

                    fov: new Range('fov', {
                        value: this.game.world.fov,
                        range: [2, 45],
                        onUpdate: (value) => {
                            this.game.world.fov = value;
                            this.game.world.resize();
                        },
                        onComplete: () => this.game.storage.savePreferences(),
                    }),

                    theme: new Range('theme', {
                        value: { cube: 0, erno: 1, dust: 2, camo: 3, rain: 4 }[
                            this.game.themes.theme
                        ],
                        range: [0, 4],
                        step: 1,
                        onUpdate: (value) => {
                            const theme = ['cube', 'erno', 'dust', 'camo', 'rain'][value];
                            this.game.themes.setTheme(theme);
                        },
                        onComplete: () => this.game.storage.savePreferences(),
                    }),

                    hue: new Range('hue', {
                        value: 0,
                        range: [0, 360],
                        onUpdate: (value) => this.game.themeEditor.updateHSL(),
                        onComplete: () => this.game.storage.savePreferences(),
                    }),

                    saturation: new Range('saturation', {
                        value: 100,
                        range: [0, 100],
                        onUpdate: (value) => this.game.themeEditor.updateHSL(),
                        onComplete: () => this.game.storage.savePreferences(),
                    }),

                    lightness: new Range('lightness', {
                        value: 50,
                        range: [0, 100],
                        onUpdate: (value) => this.game.themeEditor.updateHSL(),
                        onComplete: () => this.game.storage.savePreferences(),
                    }),
                };

                this.ranges.scramble.list.forEach((item, i) => {
                    item.innerHTML =
                        this.game.scrambler.scrambleLength[this.game.cube.size][i];
                });
            }
        }

        class Confetti {
            constructor(game) {
                this.game = game;
                this.started = 0;

                this.options = {
                    speed: { min: 0.0011, max: 0.0022 },
                    revolution: { min: 0.01, max: 0.05 },
                    size: { min: 0.1, max: 0.15 },
                    colors: [0x41aac8, 0x82ca38, 0xffef48, 0xef3923, 0xff8c0a],
                };

                this.geometry = new THREE.PlaneGeometry(1, 1);
                this.material = new THREE.MeshLambertMaterial({
                    side: THREE.DoubleSide,
                });

                this.holders = [
                    new ConfettiStage(this.game, this, 1, 20),
                    new ConfettiStage(this.game, this, -1, 30),
                ];
            }

            start() {
                if (this.started > 0) return;

                this.holders.forEach((holder) => {
                    this.game.world.scene.add(holder.holder);
                    holder.start();
                    this.started++;
                });
            }

            stop() {
                if (this.started == 0) return;

                this.holders.forEach((holder) => {
                    holder.stop(() => {
                        this.game.world.scene.remove(holder.holder);
                        this.started--;
                    });
                });
            }

            updateColors(colors) {
                this.holders.forEach((holder) => {
                    holder.options.colors.forEach((color, index) => {
                        holder.options.colors[index] =
                            colors[['D', 'F', 'R', 'B', 'L'][index]];
                    });
                });
            }
        }

        class ConfettiStage extends Animation {
            constructor(game, parent, distance, count) {
                super(false);

                this.game = game;
                this.parent = parent;

                this.distanceFromCube = distance;

                this.count = count;
                this.particles = [];

                this.holder = new THREE.Object3D();
                this.holder.rotation.copy(this.game.world.camera.rotation);

                this.object = new THREE.Object3D();
                this.holder.add(this.object);

                this.resizeViewport = this.resizeViewport.bind(this);
                this.game.world.onResize.push(this.resizeViewport);
                this.resizeViewport();

                this.geometry = this.parent.geometry;
                this.material = this.parent.material;

                this.options = this.parent.options;

                let i = this.count;
                while (i--) this.particles.push(new Particle(this));
            }

            start() {
                this.time = performance.now();
                this.playing = true;

                let i = this.count;
                while (i--) this.particles[i].reset();

                super.start();
            }

            stop(callback) {
                this.playing = false;
                this.completed = 0;
                this.callback = callback;
            }

            reset() {
                super.stop();

                this.callback();
            }

            update() {
                const now = performance.now();
                const delta = now - this.time;
                this.time = now;

                let i = this.count;

                while (i--)
                    if (!this.particles[i].completed) this.particles[i].update(delta);

                if (!this.playing && this.completed == this.count) this.reset();
            }

            resizeViewport() {
                const fovRad = this.game.world.camera.fov * THREE.Math.DEG2RAD;

                this.height =
                    2 *
                    Math.tan(fovRad / 2) *
                    (this.game.world.camera.position.length() - this.distanceFromCube);
                this.width = this.height * this.game.world.camera.aspect;

                const scale = 1 / this.game.transition.data.cameraZoom;

                this.width *= scale;
                this.height *= scale;

                this.object.position.z = this.distanceFromCube;
                this.object.position.y = this.height / 2;
            }
        }

        class Particle {
            constructor(confetti) {
                this.confetti = confetti;
                this.options = this.confetti.options;

                this.velocity = new THREE.Vector3();
                this.force = new THREE.Vector3();

                this.mesh = new THREE.Mesh(
                    this.confetti.geometry,
                    this.confetti.material.clone()
                );
                this.confetti.object.add(this.mesh);

                this.size = THREE.Math.randFloat(
                    this.options.size.min,
                    this.options.size.max
                );
                this.mesh.scale.set(this.size, this.size, this.size);

                return this;
            }

            reset(randomHeight = true) {
                this.completed = false;

                this.color = new THREE.Color(
                    this.options.colors[
                    Math.floor(Math.random() * this.options.colors.length)
                    ]
                );
                this.mesh.material.color.set(this.color);

                this.speed =
                    THREE.Math.randFloat(
                        this.options.speed.min,
                        this.options.speed.max
                    ) * -1;
                this.mesh.position.x = THREE.Math.randFloat(
                    -this.confetti.width / 2,
                    this.confetti.width / 2
                );
                this.mesh.position.y = randomHeight
                    ? THREE.Math.randFloat(this.size, this.confetti.height + this.size)
                    : this.size;

                this.revolutionSpeed = THREE.Math.randFloat(
                    this.options.revolution.min,
                    this.options.revolution.max
                );
                this.revolutionAxis = ['x', 'y', 'z'][Math.floor(Math.random() * 3)];
                this.mesh.rotation.set(
                    (Math.random() * Math.PI) / 3,
                    (Math.random() * Math.PI) / 3,
                    (Math.random() * Math.PI) / 3
                );
            }

            stop() {
                this.completed = true;
                this.confetti.completed++;
            }

            update(delta) {
                this.mesh.position.y += this.speed * delta;
                this.mesh.rotation[this.revolutionAxis] += this.revolutionSpeed;

                if (this.mesh.position.y < -this.confetti.height - this.size)
                    this.confetti.playing ? this.reset(false) : this.stop();
            }
        }

        class Scores {
            constructor(game) {
                this.game = game;

                this.data = {
                    2: {
                        scores: [],
                        solves: 0,
                        best: 0,
                        worst: 0,
                    },
                    3: {
                        scores: [],
                        solves: 0,
                        best: 0,
                        worst: 0,
                    },
                    4: {
                        scores: [],
                        solves: 0,
                        best: 0,
                        worst: 0,
                    },
                    5: {
                        scores: [],
                        solves: 0,
                        best: 0,
                        worst: 0,
                    },
                };
            }

            addScore(time) {
                const data = this.data[this.game.cube.sizeGenerated];

                data.scores.push(time);
                data.solves++;

                if (data.scores.lenght > 100) data.scores.shift();

                let bestTime = false;

                if (time < data.best || data.best === 0) {
                    data.best = time;
                    bestTime = true;
                }

                if (time > data.worst) data.worst = time;

                this.game.storage.saveScores();

                return bestTime;
            }

            calcStats() {
                const s = this.game.cube.sizeGenerated;
                const data = this.data[s];

                this.setStat('cube-size', `${s}<i>x</i>${s}<i>x</i>${s}`);
                this.setStat('total-solves', data.solves);
                this.setStat('best-time', this.convertTime(data.best));
                this.setStat('worst-time', this.convertTime(data.worst));
                this.setStat('average-5', this.getAverage(5));
                this.setStat('average-12', this.getAverage(12));
                this.setStat('average-25', this.getAverage(25));
            }

            setStat(name, value) {
                if (value === 0) value = '-';

                this.game.dom.stats.querySelector(
                    `.stats[name="${name}"] b`
                ).innerHTML = value;
            }

            getAverage(count) {
                const data = this.data[this.game.cube.sizeGenerated];

                if (data.scores.length < count) return 0;

                return this.convertTime(
                    data.scores.slice(-count).reduce((a, b) => a + b, 0) / count
                );
            }

            convertTime(time) {
                if (time <= 0) return 0;

                const seconds = parseInt((time / 1000) % 60);
                const minutes = parseInt(time / (1000 * 60));

                return minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
            }
        }

        class Storage {
            constructor(game) {
                this.game = game;

                const userVersion = localStorage.getItem('theCube_version');

                if (!userVersion || userVersion !== window.gameVersion) {
                    this.clearGame();
                    this.clearPreferences();
                    this.migrateScores();
                    localStorage.setItem('theCube_version', window.gameVersion);
                }
            }

            init() {
                this.loadPreferences();
                this.loadScores();
            }

            loadGame() {
                try {
                    const gameInProgress =
                        localStorage.getItem('theCube_playing') === 'true';

                    if (!gameInProgress) throw new Error();

                    const gameCubeData = JSON.parse(
                        localStorage.getItem('theCube_savedState')
                    );
                    const gameTime = parseInt(localStorage.getItem('theCube_time'));

                    if (!gameCubeData || gameTime === null) throw new Error();
                    if (gameCubeData.size !== this.game.cube.sizeGenerated)
                        throw new Error();

                    this.game.cube.loadFromData(gameCubeData);

                    this.game.timer.deltaTime = gameTime;

                    this.game.saved = true;
                } catch (e) {
                    this.game.saved = false;
                }
            }

            saveGame() {
                const gameInProgress = true;
                const gameCubeData = { names: [], positions: [], rotations: [] };
                const gameTime = this.game.timer.deltaTime;

                gameCubeData.size = this.game.cube.sizeGenerated;

                this.game.cube.pieces.forEach((piece) => {
                    gameCubeData.names.push(piece.name);
                    gameCubeData.positions.push(piece.position);
                    gameCubeData.rotations.push(piece.rotation.toVector3());
                });

                localStorage.setItem('theCube_playing', gameInProgress);
                localStorage.setItem(
                    'theCube_savedState',
                    JSON.stringify(gameCubeData)
                );
                localStorage.setItem('theCube_time', gameTime);
            }

            clearGame() {
                localStorage.removeItem('theCube_playing');
                localStorage.removeItem('theCube_savedState');
                localStorage.removeItem('theCube_time');
            }

            loadScores() {
                try {
                    const scoresData = JSON.parse(
                        localStorage.getItem('theCube_scores')
                    );

                    if (!scoresData) throw new Error();

                    this.game.scores.data = scoresData;
                } catch (e) { }
            }

            saveScores() {
                const scoresData = this.game.scores.data;

                localStorage.setItem('theCube_scores', JSON.stringify(scoresData));
            }

            clearScores() {
                localStorage.removeItem('theCube_scores');
            }

            migrateScores() {
                try {
                    const scoresData = JSON.parse(
                        localStorage.getItem('theCube_scoresData')
                    );
                    const scoresBest = parseInt(
                        localStorage.getItem('theCube_scoresBest')
                    );
                    const scoresWorst = parseInt(
                        localStorage.getItem('theCube_scoresWorst')
                    );
                    const scoresSolves = parseInt(
                        localStorage.getItem('theCube_scoresSolves')
                    );

                    if (!scoresData || !scoresBest || !scoresSolves || !scoresWorst)
                        return false;

                    this.game.scores.data[3].scores = scoresData;
                    this.game.scores.data[3].best = scoresBest;
                    this.game.scores.data[3].solves = scoresSolves;
                    this.game.scores.data[3].worst = scoresWorst;

                    localStorage.removeItem('theCube_scoresData');
                    localStorage.removeItem('theCube_scoresBest');
                    localStorage.removeItem('theCube_scoresWorst');
                    localStorage.removeItem('theCube_scoresSolves');
                } catch (e) { }
            }

            loadPreferences() {
                try {
                    const preferences = JSON.parse(
                        localStorage.getItem('theCube_preferences')
                    );

                    if (!preferences) throw new Error();

                    this.game.cube.size = parseInt(preferences.cubeSize);
                    this.game.controls.flipConfig = parseInt(preferences.flipConfig);
                    this.game.scrambler.dificulty = parseInt(preferences.dificulty);

                    this.game.world.fov = parseFloat(preferences.fov);
                    this.game.world.resize();

                    this.game.themes.colors = preferences.colors;
                    this.game.themes.setTheme(preferences.theme);

                    return true;
                } catch (e) {
                    this.game.cube.size = 3;
                    this.game.controls.flipConfig = 0;
                    this.game.scrambler.dificulty = 1;

                    this.game.world.fov = 10;
                    this.game.world.resize();

                    this.game.themes.setTheme('cube');

                    this.savePreferences();

                    return false;
                }
            }

            savePreferences() {
                const preferences = {
                    cubeSize: this.game.cube.size,
                    flipConfig: this.game.controls.flipConfig,
                    dificulty: this.game.scrambler.dificulty,
                    fov: this.game.world.fov,
                    theme: this.game.themes.theme,
                    colors: this.game.themes.colors,
                };

                localStorage.setItem(
                    'theCube_preferences',
                    JSON.stringify(preferences)
                );
            }

            clearPreferences() {
                localStorage.removeItem('theCube_preferences');
            }
        }

        class Themes {
            constructor(game) {
                this.game = game;
                this.theme = null;

                this.defaults = {
                    cube: {
                        U: 0xfff7ff, // white
                        D: 0xffef48, // yellow
                        F: 0xef3923, // red
                        R: 0x41aac8, // blue
                        B: 0xff8c0a, // orange
                        L: 0x82ca38, // green
                        P: 0x08101a, // piece
                        G: 0xd1d5db, // background
                    },
                    erno: {
                        U: 0xffffff,
                        D: 0xffd500,
                        F: 0xc41e3a,
                        R: 0x0051ba,
                        B: 0xff5800,
                        L: 0x009e60,
                        P: 0x08101a,
                        G: 0x8abdff,
                    },
                    dust: {
                        U: 0xfff6eb,
                        D: 0xe7c48d,
                        F: 0x8f253e,
                        R: 0x607e69,
                        B: 0xbe6f62,
                        L: 0x849f5d,
                        P: 0x08101a,
                        G: 0xe7c48d,
                    },
                    camo: {
                        U: 0xfff6eb,
                        D: 0xbfb672,
                        F: 0x37241c,
                        R: 0x718456,
                        B: 0x805831,
                        L: 0x37431d,
                        P: 0x08101a,
                        G: 0xbfb672,
                    },
                    rain: {
                        U: 0xfafaff,
                        D: 0xedb92d,
                        F: 0xce2135,
                        R: 0x449a89,
                        B: 0xec582f,
                        L: 0xa3a947,
                        P: 0x08101a,
                        G: 0x87b9ac,
                    },
                };

                this.colors = JSON.parse(JSON.stringify(this.defaults));
            }

            getColors() {
                return this.colors[this.theme];
            }

            setTheme(theme = false, force = false) {
                if (theme === this.theme && force === false) return;
                if (theme !== false) this.theme = theme;

                const colors = this.getColors();

                this.game.dom.prefs
                    .querySelectorAll('.range__handle div')
                    .forEach((range) => {
                        range.style.background =
                            '#' + colors.R.toString(16).padStart(6, '0');
                    });

                this.game.cube.updateColors(colors);

                this.game.confetti.updateColors(colors);

                this.game.dom.back.style.background =
                    '#' + colors.G.toString(16).padStart(6, '0');
            }
        }

        class ThemeEditor {
            constructor(game) {
                this.game = game;

                this.editColor = 'R';

                this.getPieceColor = this.getPieceColor.bind(this);
            }

            colorFromHSL(h, s, l) {
                h = Math.round(h);
                s = Math.round(s);
                l = Math.round(l);

                return new THREE.Color(`hsl(${h}, ${s}%, ${l}%)`);
            }

            setHSL(color = null, animate = false) {
                this.editColor = color === null ? 'R' : color;

                const hsl = new THREE.Color(
                    this.game.themes.getColors()[this.editColor]
                );

                const { h, s, l } = hsl.getHSL(hsl);
                const { hue, saturation, lightness } = this.game.preferences.ranges;

                if (animate) {
                    const ho = hue.value / 360;
                    const so = saturation.value / 100;
                    const lo = lightness.value / 100;

                    const colorOld = this.colorFromHSL(
                        hue.value,
                        saturation.value,
                        lightness.value
                    );

                    if (this.tweenHSL) this.tweenHSL.stop();

                    this.tweenHSL = new Tween({
                        duration: 200,
                        easing: Easing.Sine.Out(),
                        onUpdate: (tween) => {
                            hue.setValue((ho + (h - ho) * tween.value) * 360);
                            saturation.setValue((so + (s - so) * tween.value) * 100);
                            lightness.setValue((lo + (l - lo) * tween.value) * 100);

                            const colorTween = colorOld.clone().lerp(hsl, tween.value);

                            const colorTweenStyle = colorTween.getStyle();
                            const colorTweenHex = colorTween.getHSL(colorTween);

                            hue.handle.style.color = colorTweenStyle;
                            saturation.handle.style.color = colorTweenStyle;
                            lightness.handle.style.color = colorTweenStyle;

                            saturation.track.style.color = this.colorFromHSL(
                                colorTweenHex.h * 360,
                                100,
                                50
                            ).getStyle();
                            lightness.track.style.color = this.colorFromHSL(
                                colorTweenHex.h * 360,
                                colorTweenHex.s * 100,
                                50
                            ).getStyle();

                            this.game.dom.theme.style.display = 'none';
                            this.game.dom.theme.offsetHeight;
                            this.game.dom.theme.style.display = '';
                        },
                        onComplete: () => {
                            this.updateHSL();
                            this.game.storage.savePreferences();
                        },
                    });
                } else {
                    hue.setValue(h * 360);
                    saturation.setValue(s * 100);
                    lightness.setValue(l * 100);

                    this.updateHSL();
                    this.game.storage.savePreferences();
                }
            }

            updateHSL() {
                const { hue, saturation, lightness } = this.game.preferences.ranges;

                const h = hue.value;
                const s = saturation.value;
                const l = lightness.value;

                const color = this.colorFromHSL(h, s, l).getStyle();

                hue.handle.style.color = color;
                saturation.handle.style.color = color;
                lightness.handle.style.color = color;

                saturation.track.style.color = this.colorFromHSL(
                    h,
                    100,
                    50
                ).getStyle();
                lightness.track.style.color = this.colorFromHSL(h, s, 50).getStyle();

                this.game.dom.theme.style.display = 'none';
                this.game.dom.theme.offsetHeight;
                this.game.dom.theme.style.display = '';

                const theme = this.game.themes.theme;

                this.game.themes.colors[theme][this.editColor] = this.colorFromHSL(
                    h,
                    s,
                    l
                ).getHex();
                this.game.themes.setTheme();
            }

            colorPicker(enable) {
                if (enable) {
                    this.game.dom.game.addEventListener(
                        'click',
                        this.getPieceColor,
                        false
                    );
                } else {
                    this.game.dom.game.removeEventListener(
                        'click',
                        this.getPieceColor,
                        false
                    );
                }
            }

            getPieceColor(event) {
                const clickEvent = event.touches
                    ? event.touches[0] || event.changedTouches[0]
                    : event;

                const clickPosition = new THREE.Vector2(
                    clickEvent.pageX,
                    clickEvent.pageY
                );

                let edgeIntersect = this.game.controls.getIntersect(
                    clickPosition,
                    this.game.cube.edges,
                    true
                );
                let pieceIntersect = this.game.controls.getIntersect(
                    clickPosition,
                    this.game.cube.cubes,
                    true
                );

                if (edgeIntersect !== false) {
                    const edge = edgeIntersect.object;

                    const position = edge.parent
                        .localToWorld(edge.position.clone())
                        .sub(this.game.cube.object.position)
                        .sub(this.game.cube.animator.position);

                    const mainAxis = this.game.controls.getMainAxis(position);
                    if (position.multiplyScalar(2).round()[mainAxis] < 1)
                        edgeIntersect = false;
                }

                const name = edgeIntersect
                    ? edgeIntersect.object.name
                    : pieceIntersect
                        ? 'P'
                        : 'G';

                this.setHSL(name, true);
            }

            resetTheme() {
                this.game.themes.colors[this.game.themes.theme] = JSON.parse(
                    JSON.stringify(this.game.themes.defaults[this.game.themes.theme])
                );

                this.game.themes.setTheme();

                this.setHSL(this.editColor, true);
            }
        }

        const States = {
            3: {
                checkerboard: {
                    names: [
                        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
                        19, 20, 21, 22, 23, 24, 25, 26,
                    ],
                    positions: [
                        { x: 1 / 3, y: -1 / 3, z: 1 / 3 },
                        { x: -1 / 3, y: 1 / 3, z: 0 },
                        { x: 1 / 3, y: -1 / 3, z: -1 / 3 },
                        { x: -1 / 3, y: 0, z: -1 / 3 },
                        { x: 1 / 3, y: 0, z: 0 },
                        { x: -1 / 3, y: 0, z: 1 / 3 },
                        { x: 1 / 3, y: 1 / 3, z: 1 / 3 },
                        { x: -1 / 3, y: -1 / 3, z: 0 },
                        { x: 1 / 3, y: 1 / 3, z: -1 / 3 },
                        { x: 0, y: 1 / 3, z: -1 / 3 },
                        { x: 0, y: -1 / 3, z: 0 },
                        { x: 0, y: 1 / 3, z: 1 / 3 },
                        { x: 0, y: 0, z: 1 / 3 },
                        { x: 0, y: 0, z: 0 },
                        { x: 0, y: 0, z: -1 / 3 },
                        { x: 0, y: -1 / 3, z: -1 / 3 },
                        { x: 0, y: 1 / 3, z: 0 },
                        { x: 0, y: -1 / 3, z: 1 / 3 },
                        { x: -1 / 3, y: -1 / 3, z: 1 / 3 },
                        { x: 1 / 3, y: 1 / 3, z: 0 },
                        { x: -1 / 3, y: -1 / 3, z: -1 / 3 },
                        { x: 1 / 3, y: 0, z: -1 / 3 },
                        { x: -1 / 3, y: 0, z: 0 },
                        { x: 1 / 3, y: 0, z: 1 / 3 },
                        { x: -1 / 3, y: 1 / 3, z: 1 / 3 },
                        { x: 1 / 3, y: -1 / 3, z: 0 },
                        { x: -1 / 3, y: 1 / 3, z: -1 / 3 },
                    ],
                    rotations: [
                        { x: -Math.PI, y: 0, z: Math.PI },
                        { x: Math.PI, y: 0, z: 0 },
                        { x: -Math.PI, y: 0, z: Math.PI },
                        { x: 0, y: 0, z: 0 },
                        { x: 0, y: 0, z: Math.PI },
                        { x: 0, y: 0, z: 0 },
                        { x: -Math.PI, y: 0, z: Math.PI },
                        { x: Math.PI, y: 0, z: 0 },
                        { x: -Math.PI, y: 0, z: Math.PI },
                        { x: 0, y: 0, z: Math.PI },
                        { x: 0, y: 0, z: 0 },
                        { x: 0, y: 0, z: Math.PI },
                        { x: -Math.PI, y: 0, z: 0 },
                        { x: Math.PI, y: 0, z: Math.PI },
                        { x: Math.PI, y: 0, z: 0 },
                        { x: 0, y: 0, z: Math.PI },
                        { x: 0, y: 0, z: 0 },
                        { x: 0, y: 0, z: Math.PI },
                        { x: Math.PI, y: 0, z: Math.PI },
                        { x: -Math.PI, y: 0, z: 0 },
                        { x: Math.PI, y: 0, z: Math.PI },
                        { x: 0, y: 0, z: 0 },
                        { x: 0, y: 0, z: Math.PI },
                        { x: 0, y: 0, z: 0 },
                        { x: Math.PI, y: 0, z: Math.PI },
                        { x: -Math.PI, y: 0, z: 0 },
                        { x: Math.PI, y: 0, z: Math.PI },
                    ],
                    size: 3,
                },
            },
        };

        class IconsConverter {
            constructor(options) {
                options = Object.assign(
                    {
                        tagName: 'icon',
                        className: 'icon',
                        styles: false,
                        icons: {},
                        observe: false,
                        convert: false,
                    },
                    options || {}
                );

                this.tagName = options.tagName;
                this.className = options.className;
                this.icons = options.icons;

                this.svgTag = document.createElementNS(
                    'http://www.w3.org/2000/svg',
                    'svg'
                );
                this.svgTag.setAttribute('class', this.className);

                if (options.styles) this.addStyles();
                if (options.convert) this.convertAllIcons();

                if (options.observe) {
                    const MutationObserver =
                        window.MutationObserver || window.WebKitMutationObserver;
                    this.observer = new MutationObserver((mutations) => {
                        this.convertAllIcons();
                    });
                    this.observer.observe(document.documentElement, {
                        childList: true,
                        subtree: true,
                    });
                }

                return this;
            }

            convertAllIcons() {
                document.querySelectorAll(this.tagName).forEach((icon) => {
                    this.convertIcon(icon);
                });
            }

            convertIcon(icon) {
                const svgData = this.icons[icon.attributes[0].localName];

                if (typeof svgData === 'undefined') return;

                const svg = this.svgTag.cloneNode(true);
                const viewBox = svgData.viewbox.split(' ');

                svg.setAttributeNS(null, 'viewBox', svgData.viewbox);
                svg.style.width = viewBox[2] / viewBox[3] + 'em';
                svg.style.height = '1em';
                svg.innerHTML = svgData.content;

                icon.parentNode.replaceChild(svg, icon);
            }

            addStyles() {
                const style = document.createElement('style');
                style.innerHTML = `.${this.className} { display: inline-block; font-size: inherit; overflow: visible; vertical-align: -0.125em; preserveAspectRatio: none; }`;
                document.head.appendChild(style);
            }
        }

        const Icons = new IconsConverter({
            icons: {
                settings: {
                    viewbox: '0 0 512 512',
                    content:
                        '<path fill="currentColor" d="M444.788 291.1l42.616 24.599c4.867 2.809 7.126 8.618 5.459 13.985-11.07 35.642-29.97 67.842-54.689 94.586a12.016 12.016 0 0 1-14.832 2.254l-42.584-24.595a191.577 191.577 0 0 1-60.759 35.13v49.182a12.01 12.01 0 0 1-9.377 11.718c-34.956 7.85-72.499 8.256-109.219.007-5.49-1.233-9.403-6.096-9.403-11.723v-49.184a191.555 191.555 0 0 1-60.759-35.13l-42.584 24.595a12.016 12.016 0 0 1-14.832-2.254c-24.718-26.744-43.619-58.944-54.689-94.586-1.667-5.366.592-11.175 5.459-13.985L67.212 291.1a193.48 193.48 0 0 1 0-70.199l-42.616-24.599c-4.867-2.809-7.126-8.618-5.459-13.985 11.07-35.642 29.97-67.842 54.689-94.586a12.016 12.016 0 0 1 14.832-2.254l42.584 24.595a191.577 191.577 0 0 1 60.759-35.13V25.759a12.01 12.01 0 0 1 9.377-11.718c34.956-7.85 72.499-8.256 109.219-.007 5.49 1.233 9.403 6.096 9.403 11.723v49.184a191.555 191.555 0 0 1 60.759 35.13l42.584-24.595a12.016 12.016 0 0 1 14.832 2.254c24.718 26.744 43.619 58.944 54.689 94.586 1.667 5.366-.592 11.175-5.459 13.985L444.788 220.9a193.485 193.485 0 0 1 0 70.2zM336 256c0-44.112-35.888-80-80-80s-80 35.888-80 80 35.888 80 80 80 80-35.888 80-80z" />',
                },
                back: {
                    viewbox: '0 0 512 512',
                    content:
                        '<path transform="translate(512, 0) scale(-1,1)" fill="currentColor" d="M503.691 189.836L327.687 37.851C312.281 24.546 288 35.347 288 56.015v80.053C127.371 137.907 0 170.1 0 322.326c0 61.441 39.581 122.309 83.333 154.132 13.653 9.931 33.111-2.533 28.077-18.631C66.066 312.814 132.917 274.316 288 272.085V360c0 20.7 24.3 31.453 39.687 18.164l176.004-152c11.071-9.562 11.086-26.753 0-36.328z" />',
                },
                trophy: {
                    viewbox: '0 0 576 512',
                    content:
                        '<path fill="currentColor" d="M552 64H448V24c0-13.3-10.7-24-24-24H152c-13.3 0-24 10.7-24 24v40H24C10.7 64 0 74.7 0 88v56c0 66.5 77.9 131.7 171.9 142.4C203.3 338.5 240 360 240 360v72h-48c-35.3 0-64 20.7-64 56v12c0 6.6 5.4 12 12 12h296c6.6 0 12-5.4 12-12v-12c0-35.3-28.7-56-64-56h-48v-72s36.7-21.5 68.1-73.6C498.4 275.6 576 210.3 576 144V88c0-13.3-10.7-24-24-24zM64 144v-16h64.2c1 32.6 5.8 61.2 12.8 86.2-47.5-16.4-77-49.9-77-70.2zm448 0c0 20.2-29.4 53.8-77 70.2 7-25 11.8-53.6 12.8-86.2H512v16zm-127.3 4.7l-39.6 38.6 9.4 54.6c1.7 9.8-8.7 17.2-17.4 12.6l-49-25.8-49 25.8c-8.8 4.6-19.1-2.9-17.4-12.6l9.4-54.6-39.6-38.6c-7.1-6.9-3.2-19 6.7-20.5l54.8-8 24.5-49.6c4.4-8.9 17.1-8.9 21.5 0l24.5 49.6 54.8 8c9.6 1.5 13.5 13.6 6.4 20.5z" />',
                },
                cancel: {
                    viewbox: '0 0 352 512',
                    content:
                        '<path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z" />',
                },
                theme: {
                    viewbox: '0 0 512 512',
                    content:
                        '<path fill="currentColor" d="M204.3 5C104.9 24.4 24.8 104.3 5.2 203.4c-37 187 131.7 326.4 258.8 306.7 41.2-6.4 61.4-54.6 42.5-91.7-23.1-45.4 9.9-98.4 60.9-98.4h79.7c35.8 0 64.8-29.6 64.9-65.3C511.5 97.1 368.1-26.9 204.3 5zM96 320c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm32-128c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128-64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128 64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z"/>',
                },
                reset: {
                    viewbox: '0 0 512 512',
                    content:
                        '<path fill="currentColor" d="M370.72 133.28C339.458 104.008 298.888 87.962 255.848 88c-77.458.068-144.328 53.178-162.791 126.85-1.344 5.363-6.122 9.15-11.651 9.15H24.103c-7.498 0-13.194-6.807-11.807-14.176C33.933 94.924 134.813 8 256 8c66.448 0 126.791 26.136 171.315 68.685L463.03 40.97C478.149 25.851 504 36.559 504 57.941V192c0 13.255-10.745 24-24 24H345.941c-21.382 0-32.09-25.851-16.971-40.971l41.75-41.749zM32 296h134.059c21.382 0 32.09 25.851 16.971 40.971l-41.75 41.75c31.262 29.273 71.835 45.319 114.876 45.28 77.418-.07 144.315-53.144 162.787-126.849 1.344-5.363 6.122-9.15 11.651-9.15h57.304c7.498 0 13.194 6.807 11.807 14.176C478.067 417.076 377.187 504 256 504c-66.448 0-126.791-26.136-171.315-68.685L48.97 471.03C33.851 486.149 8 475.441 8 454.059V320c0-13.255 10.745-24 24-24z" />',
                },
                trash: {
                    viewbox: '0 0 448 512',
                    content:
                        '<path fill="currentColor" d="M432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16zM53.2 467a48 48 0 0 0 47.9 45h245.8a48 48 0 0 0 47.9-45L416 128H32z" />',
                },
            },

            convert: true,
        });

        const STATE = {
            Menu: 0,
            Playing: 1,
            Complete: 2,
            Stats: 3,
            Prefs: 4,
            Theme: 5,
        };

        const BUTTONS = {
            Menu: ['stats', 'prefs'],
            Playing: ['back'],
            Complete: [],
            Stats: [],
            Prefs: ['back', 'theme'],
            Theme: ['back', 'reset'],
            None: [],
        };

        const SHOW = true;
        const HIDE = false;

        class Game {
            constructor() {
                this.dom = {
                    ui: document.querySelector('.ui'),
                    game: document.querySelector('.ui__game'),
                    back: document.querySelector('.ui__background'),
                    prefs: document.querySelector('.ui__prefs'),
                    theme: document.querySelector('.ui__theme'),
                    stats: document.querySelector('.ui__stats'),
                    texts: {
                        title: document.querySelector('.text--title'),
                        note: document.querySelector('.text--note'),
                        timer: document.querySelector('.text--timer'),
                        complete: document.querySelector('.text--complete'),
                        best: document.querySelector('.text--best-time'),
                        theme: document.querySelector('.text--theme'),
                    },
                    buttons: {
                        prefs: document.querySelector('.btn--prefs'),
                        back: document.querySelector('.btn--back'),
                        stats: document.querySelector('.btn--stats'),
                        reset: document.querySelector('.btn--reset'),
                        theme: document.querySelector('.btn--theme'),
                    },
                };

                this.world = new World(this);
                this.cube = new Cube(this);
                this.controls = new Controls(this);
                this.scrambler = new Scrambler(this);
                this.transition = new Transition(this);
                this.timer = new Timer(this);
                this.preferences = new Preferences(this);
                this.scores = new Scores(this);
                this.storage = new Storage(this);
                this.confetti = new Confetti(this);
                this.themes = new Themes(this);
                this.themeEditor = new ThemeEditor(this);

                this.initActions();

                this.state = STATE.Menu;
                this.newGame = false;
                this.saved = false;

                this.storage.init();
                this.preferences.init();
                this.cube.init();
                this.transition.init();

                this.storage.loadGame();
                this.scores.calcStats();

                setTimeout(() => {
                    this.transition.float();
                    this.transition.cube(SHOW);

                    setTimeout(() => this.transition.title(SHOW), 700);
                    setTimeout(
                        () => this.transition.buttons(BUTTONS.Menu, BUTTONS.None),
                        1000
                    );
                }, 500);
            }

            initActions() {
                let tappedTwice = false;

                this.dom.game.addEventListener(
                    'click',
                    (event) => {
                        if (this.transition.activeTransitions > 0) return;
                        if (this.state === STATE.Playing) return;

                        if (this.state === STATE.Menu) {
                            if (!tappedTwice) {
                                tappedTwice = true;
                                setTimeout(() => (tappedTwice = false), 300);
                                return false;
                            }

                            this.game(SHOW);
                        } else if (this.state === STATE.Complete) {
                            this.complete(HIDE);
                        } else if (this.state === STATE.Stats) {
                            this.stats(HIDE);
                        }
                    },
                    false
                );

                this.controls.onMove = () => {
                    if (this.newGame) {
                        this.timer.start(true);
                        this.newGame = false;
                    }
                };

                this.dom.buttons.back.onclick = (event) => {
                    if (this.transition.activeTransitions > 0) return;

                    if (this.state === STATE.Playing) {
                        this.game(HIDE);
                    } else if (this.state === STATE.Prefs) {
                        this.prefs(HIDE);
                    } else if (this.state === STATE.Theme) {
                        this.theme(HIDE);
                    }
                };

                this.dom.buttons.reset.onclick = (event) => {
                    if (this.state === STATE.Theme) {
                        this.themeEditor.resetTheme();
                    }
                };

                this.dom.buttons.prefs.onclick = (event) => this.prefs(SHOW);

                this.dom.buttons.theme.onclick = (event) => this.theme(SHOW);

                this.dom.buttons.stats.onclick = (event) => this.stats(SHOW);

                this.controls.onSolved = () => this.complete(SHOW);
            }

            game(show) {
                if (show) {
                    if (!this.saved) {
                        this.scrambler.scramble();
                        this.controls.scrambleCube();
                        this.newGame = true;
                    }

                    const duration = this.saved
                        ? 0
                        : this.scrambler.converted.length *
                        (this.controls.flipSpeeds[0] + 10);

                    this.state = STATE.Playing;
                    this.saved = true;

                    this.transition.buttons(BUTTONS.None, BUTTONS.Menu);

                    this.transition.zoom(STATE.Playing, duration);
                    this.transition.title(HIDE);

                    setTimeout(() => {
                        this.transition.timer(SHOW);
                        this.transition.buttons(BUTTONS.Playing, BUTTONS.None);
                    }, this.transition.durations.zoom - 1000);

                    setTimeout(() => {
                        this.controls.enable();
                        if (!this.newGame) this.timer.start(true);
                    }, this.transition.durations.zoom);
                } else {
                    this.state = STATE.Menu;

                    this.transition.buttons(BUTTONS.Menu, BUTTONS.Playing);

                    this.transition.zoom(STATE.Menu, 0);

                    this.controls.disable();
                    if (!this.newGame) this.timer.stop();
                    this.transition.timer(HIDE);

                    setTimeout(
                        () => this.transition.title(SHOW),
                        this.transition.durations.zoom - 1000
                    );

                    this.playing = false;
                    this.controls.disable();
                }
            }

            prefs(show) {
                if (show) {
                    if (this.transition.activeTransitions > 0) return;

                    this.state = STATE.Prefs;

                    this.transition.buttons(BUTTONS.Prefs, BUTTONS.Menu);

                    this.transition.title(HIDE);
                    this.transition.cube(HIDE);

                    setTimeout(() => this.transition.preferences(SHOW), 1000);
                } else {
                    this.cube.resize();

                    this.state = STATE.Menu;

                    this.transition.buttons(BUTTONS.Menu, BUTTONS.Prefs);

                    this.transition.preferences(HIDE);

                    setTimeout(() => this.transition.cube(SHOW), 500);
                    setTimeout(() => this.transition.title(SHOW), 1200);
                }
            }

            theme(show) {
                this.themeEditor.colorPicker(show);

                if (show) {
                    if (this.transition.activeTransitions > 0) return;

                    this.cube.loadFromData(States['3']['checkerboard']);

                    this.themeEditor.setHSL(null, false);

                    this.state = STATE.Theme;

                    this.transition.buttons(BUTTONS.Theme, BUTTONS.Prefs);

                    this.transition.preferences(HIDE);

                    setTimeout(() => this.transition.cube(SHOW, true), 500);
                    setTimeout(() => this.transition.theming(SHOW), 1000);
                } else {
                    this.state = STATE.Prefs;

                    this.transition.buttons(BUTTONS.Prefs, BUTTONS.Theme);

                    this.transition.cube(HIDE, true);
                    this.transition.theming(HIDE);

                    setTimeout(() => this.transition.preferences(SHOW), 1000);
                    setTimeout(() => {
                        const gameCubeData = JSON.parse(
                            localStorage.getItem('theCube_savedState')
                        );

                        if (!gameCubeData) {
                            this.cube.resize(true);
                            return;
                        }

                        this.cube.loadFromData(gameCubeData);
                    }, 1500);
                }
            }

            stats(show) {
                if (show) {
                    if (this.transition.activeTransitions > 0) return;

                    this.state = STATE.Stats;

                    this.transition.buttons(BUTTONS.Stats, BUTTONS.Menu);

                    this.transition.title(HIDE);
                    this.transition.cube(HIDE);

                    setTimeout(() => this.transition.stats(SHOW), 1000);
                } else {
                    this.state = STATE.Menu;

                    this.transition.buttons(BUTTONS.Menu, BUTTONS.None);

                    this.transition.stats(HIDE);

                    setTimeout(() => this.transition.cube(SHOW), 500);
                    setTimeout(() => this.transition.title(SHOW), 1200);
                }
            }

            complete(show) {
                if (show) {
                    this.transition.buttons(BUTTONS.Complete, BUTTONS.Playing);

                    this.state = STATE.Complete;
                    this.saved = false;

                    this.controls.disable();
                    this.timer.stop();
                    this.storage.clearGame();

                    this.bestTime = this.scores.addScore(this.timer.deltaTime);

                    this.transition.zoom(STATE.Menu, 0);
                    this.transition.elevate(SHOW);

                    setTimeout(() => {
                        this.transition.complete(SHOW, this.bestTime);
                        this.confetti.start();
                    }, 1000);
                } else {
                    this.state = STATE.Stats;
                    this.saved = false;

                    this.transition.timer(HIDE);
                    this.transition.complete(HIDE, this.bestTime);
                    this.transition.cube(HIDE);
                    this.timer.reset();

                    setTimeout(() => {
                        this.cube.reset();
                        this.confetti.stop();

                        this.transition.stats(SHOW);
                        this.transition.elevate(0);
                    }, 1000);

                    return false;
                }
            }
        }

        window.version = '0.99.2';
        window.game = new Game();
    </script>
</body>

</html>